1 /*****************************************************************************
3 * Nagios check_icmp plugin
6 * Copyright (c) 2005-2008 Nagios Plugins Development Team
7 * Original Author : Andreas Ericsson <ae@op5.se>
11 * This file contains the check_icmp plugin
13 * Relevant RFC's: 792 (ICMP), 791 (IP)
15 * This program was modeled somewhat after the check_icmp program,
16 * which was in turn a hack of fping (www.fping.org) but has been
17 * completely rewritten since to generate higher precision rta values,
18 * and support several different modes as well as setting ttl to control.
19 * redundant routes. The only remainders of fping is currently a few
23 * This program is free software: you can redistribute it and/or modify
24 * it under the terms of the GNU General Public License as published by
25 * the Free Software Foundation, either version 3 of the License, or
26 * (at your option) any later version.
28 * This program is distributed in the hope that it will be useful,
29 * but WITHOUT ANY WARRANTY; without even the implied warranty of
30 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
31 * GNU General Public License for more details.
33 * You should have received a copy of the GNU General Public License
34 * along with this program. If not, see <http://www.gnu.org/licenses/>.
37 *****************************************************************************/
39 /* progname may change */
40 /* char *progname = "check_icmp"; */
42 const char *copyright
= "2005-2008";
43 const char *email
= "nagiosplug-devel@lists.sourceforge.net";
45 /** nagios plugins basic includes */
51 #include <sys/sockio.h>
53 #include <sys/ioctl.h>
55 #include <sys/types.h>
65 #include <sys/socket.h>
67 #include <netinet/in_systm.h>
68 #include <netinet/in.h>
69 #include <netinet/ip.h>
70 #include <netinet/ip_icmp.h>
71 #include <arpa/inet.h>
76 /** sometimes undefined system macros (quite a few, actually) **/
81 # define INADDR_NONE (in_addr_t)(-1)
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
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
107 # define DBL_MAX 9.9999999999e999
110 typedef unsigned short range_t
; /* type for get_range() -- unimplemented */
112 typedef struct rta_host
{
113 unsigned short id
; /* id in **table, and icmp pkts */
114 char *name
; /* arg used for adding this host */
115 char *msg
; /* icmp error message, if any */
116 struct sockaddr_in saddr_in
; /* the address of this host */
117 struct in_addr error_addr
; /* stores address of error replies */
118 unsigned long long time_waited
; /* total time waited, in usecs */
119 unsigned int icmp_sent
, icmp_recv
, icmp_lost
; /* counters */
120 unsigned char icmp_type
, icmp_code
; /* type and code from errors */
121 unsigned short flags
; /* control/status flags */
122 double rta
; /* measured RTA */
123 double rtmax
; /* max rtt */
124 double rtmin
; /* min rtt */
125 unsigned char pl
; /* measured packet loss */
126 struct rta_host
*next
; /* linked list */
129 #define FLAG_LOST_CAUSE 0x01 /* decidedly dead target. */
131 /* threshold structure. all values are maximum allowed, exclusive */
132 typedef struct threshold
{
133 unsigned char pl
; /* max allowed packet loss in percent */
134 unsigned int rta
; /* roundtrip time average, microseconds */
137 /* the data structure */
138 typedef struct icmp_ping_data
{
139 struct timeval stime
; /* timestamp (saved in protocol struct as well) */
140 unsigned short ping_id
;
143 /* the different modes of this program are as follows:
144 * MODE_RTA: send all packets no matter what (mimic check_icmp and check_ping)
145 * MODE_HOSTCHECK: Return immediately upon any sign of life
146 * In addition, sends packets to ALL addresses assigned
147 * to this host (as returned by gethostbyname() or
148 * gethostbyaddr() and expects one host only to be checked at
149 * a time. Therefore, any packet response what so ever will
150 * count as a sign of life, even when received outside
151 * crit.rta limit. Do not misspell any additional IP's.
152 * MODE_ALL: Requires packets from ALL requested IP to return OK (default).
153 * MODE_ICMP: implement something similar to check_icmp (MODE_RTA without
154 * tcp and udp args does this)
157 #define MODE_HOSTCHECK 1
161 /* the different ping types we can do
162 * TODO: investigate ARP ping as well */
168 #define MIN_PING_DATA_SIZE sizeof(struct icmp_ping_data)
169 #define MAX_IP_PKT_SIZE 65536 /* (theoretical) max IP packet size */
170 #define IP_HDR_SIZE 20
171 #define MAX_PING_DATA (MAX_IP_PKT_SIZE - IP_HDR_SIZE - ICMP_MINLEN)
172 #define DEFAULT_PING_DATA_SIZE (MIN_PING_DATA_SIZE + 44)
174 /* various target states */
175 #define TSTATE_INACTIVE 0x01 /* don't ping this host anymore */
176 #define TSTATE_WAITING 0x02 /* unanswered packets on the wire */
177 #define TSTATE_ALIVE 0x04 /* target is alive (has answered something) */
178 #define TSTATE_UNREACH 0x08
181 void print_help (void);
182 void print_usage (void);
183 static u_int
get_timevar(const char *);
184 static u_int
get_timevaldiff(struct timeval
*, struct timeval
*);
185 static in_addr_t
get_ip_address(const char *);
186 static int wait_for_reply(int, u_int
);
187 static int recvfrom_wto(int, void *, unsigned int, struct sockaddr
*, u_int
*);
188 static int send_icmp_ping(int, struct rta_host
*);
189 static int get_threshold(char *str
, threshold
*th
);
190 static void run_checks(void);
191 static void set_source_ip(char *);
192 static int add_target(char *);
193 static int add_target_ip(char *, struct in_addr
*);
194 static int handle_random_icmp(unsigned char *, struct sockaddr_in
*);
195 static unsigned short icmp_checksum(unsigned short *, int);
196 static void finish(int);
197 static void crash(const char *, ...);
200 extern int optind
, opterr
, optopt
;
202 extern char **environ
;
204 /** global variables **/
205 static struct rta_host
**table
, *cursor
, *list
;
206 static threshold crit
= {80, 500000}, warn
= {40, 200000};
207 static int mode
, protocols
, sockets
, debug
= 0, timeout
= 10;
208 static unsigned short icmp_data_size
= DEFAULT_PING_DATA_SIZE
;
209 static unsigned short icmp_pkt_size
= DEFAULT_PING_DATA_SIZE
+ ICMP_MINLEN
;
211 static unsigned int icmp_sent
= 0, icmp_recv
= 0, icmp_lost
= 0;
212 #define icmp_pkts_en_route (icmp_sent - (icmp_recv + icmp_lost))
213 static unsigned short targets_down
= 0, targets
= 0, packets
= 0;
214 #define targets_alive (targets - targets_down)
215 static unsigned int retry_interval
, pkt_interval
, target_interval
;
216 static int icmp_sock
, tcp_sock
, udp_sock
, status
= STATE_OK
;
218 static struct timezone tz
;
219 static struct timeval prog_start
;
220 static unsigned long long max_completion_time
= 0;
221 static unsigned char ttl
= 0; /* outgoing ttl */
222 static unsigned int warn_down
= 1, crit_down
= 1; /* host down threshold values */
223 static int min_hosts_alive
= -1;
224 float pkt_backoff_factor
= 1.5;
225 float target_backoff_factor
= 1.5;
229 crash(const char *fmt
, ...)
233 printf("%s: ", progname
);
239 if(errno
) printf(": %s", strerror(errno
));
247 get_icmp_error_msg(unsigned char icmp_type
, unsigned char icmp_code
)
249 const char *msg
= "unreachable";
251 if(debug
> 1) printf("get_icmp_error_msg(%u, %u)\n", icmp_type
, icmp_code
);
255 case ICMP_UNREACH_NET
: msg
= "Net unreachable"; break;
256 case ICMP_UNREACH_HOST
: msg
= "Host unreachable"; break;
257 case ICMP_UNREACH_PROTOCOL
: msg
= "Protocol unreachable (firewall?)"; break;
258 case ICMP_UNREACH_PORT
: msg
= "Port unreachable (firewall?)"; break;
259 case ICMP_UNREACH_NEEDFRAG
: msg
= "Fragmentation needed"; break;
260 case ICMP_UNREACH_SRCFAIL
: msg
= "Source route failed"; break;
261 case ICMP_UNREACH_ISOLATED
: msg
= "Source host isolated"; break;
262 case ICMP_UNREACH_NET_UNKNOWN
: msg
= "Unknown network"; break;
263 case ICMP_UNREACH_HOST_UNKNOWN
: msg
= "Unknown host"; break;
264 case ICMP_UNREACH_NET_PROHIB
: msg
= "Network denied (firewall?)"; break;
265 case ICMP_UNREACH_HOST_PROHIB
: msg
= "Host denied (firewall?)"; break;
266 case ICMP_UNREACH_TOSNET
: msg
= "Bad TOS for network (firewall?)"; break;
267 case ICMP_UNREACH_TOSHOST
: msg
= "Bad TOS for host (firewall?)"; break;
268 case ICMP_UNREACH_FILTER_PROHIB
: msg
= "Prohibited by filter (firewall)"; break;
269 case ICMP_UNREACH_HOST_PRECEDENCE
: msg
= "Host precedence violation"; break;
270 case ICMP_UNREACH_PRECEDENCE_CUTOFF
: msg
= "Precedence cutoff"; break;
271 default: msg
= "Invalid code"; break;
276 /* really 'out of reach', or non-existant host behind a router serving
277 * two different subnets */
279 case ICMP_TIMXCEED_INTRANS
: msg
= "Time to live exceeded in transit"; break;
280 case ICMP_TIMXCEED_REASS
: msg
= "Fragment reassembly time exceeded"; break;
281 default: msg
= "Invalid code"; break;
285 case ICMP_SOURCEQUENCH
: msg
= "Transmitting too fast"; break;
286 case ICMP_REDIRECT
: msg
= "Redirect (change route)"; break;
287 case ICMP_PARAMPROB
: msg
= "Bad IP header (required option absent)"; break;
289 /* the following aren't error messages, so ignore */
291 case ICMP_TSTAMPREPLY
:
296 default: msg
= ""; break;
303 handle_random_icmp(unsigned char *packet
, struct sockaddr_in
*addr
)
305 struct icmp p
, sent_icmp
;
306 struct rta_host
*host
= NULL
;
308 memcpy(&p
, packet
, sizeof(p
));
309 if(p
.icmp_type
== ICMP_ECHO
&& ntohs(p
.icmp_id
) == pid
) {
310 /* echo request from us to us (pinging localhost) */
314 if(debug
) printf("handle_random_icmp(%p, %p)\n", (void *)&p
, (void *)addr
);
316 /* only handle a few types, since others can't possibly be replies to
317 * us in a sane network (if it is anyway, it will be counted as lost
318 * at summary time, but not as quickly as a proper response */
319 /* TIMXCEED can be an unreach from a router with multiple IP's which
320 * serves two different subnets on the same interface and a dead host
321 * on one net is pinged from the other. The router will respond to
322 * itself and thus set TTL=0 so as to not loop forever. Even when
323 * TIMXCEED actually sends a proper icmp response we will have passed
324 * too many hops to have a hope of reaching it later, in which case it
325 * indicates overconfidence in the network, poor routing or both. */
326 if(p
.icmp_type
!= ICMP_UNREACH
&& p
.icmp_type
!= ICMP_TIMXCEED
&&
327 p
.icmp_type
!= ICMP_SOURCEQUENCH
&& p
.icmp_type
!= ICMP_PARAMPROB
)
332 /* might be for us. At least it holds the original package (according
333 * to RFC 792). If it isn't, just ignore it */
334 memcpy(&sent_icmp
, packet
+ 28, sizeof(sent_icmp
));
335 if(sent_icmp
.icmp_type
!= ICMP_ECHO
|| ntohs(sent_icmp
.icmp_id
) != pid
||
336 ntohs(sent_icmp
.icmp_seq
) >= targets
*packets
)
338 if(debug
) printf("Packet is no response to a packet we sent\n");
342 /* it is indeed a response for us */
343 host
= table
[ntohs(sent_icmp
.icmp_seq
)/packets
];
345 printf("Received \"%s\" from %s for ICMP ECHO sent to %s.\n",
346 get_icmp_error_msg(p
.icmp_type
, p
.icmp_code
),
347 inet_ntoa(addr
->sin_addr
), host
->name
);
352 /* don't spend time on lost hosts any more */
353 if(host
->flags
& FLAG_LOST_CAUSE
) return 0;
355 /* source quench means we're sending too fast, so increase the
356 * interval and mark this packet lost */
357 if(p
.icmp_type
== ICMP_SOURCEQUENCH
) {
358 pkt_interval
*= pkt_backoff_factor
;
359 target_interval
*= target_backoff_factor
;
363 host
->flags
|= FLAG_LOST_CAUSE
;
365 host
->icmp_type
= p
.icmp_type
;
366 host
->icmp_code
= p
.icmp_code
;
367 host
->error_addr
.s_addr
= addr
->sin_addr
.s_addr
;
373 main(int argc
, char **argv
)
378 int icmp_sockerrno
, udp_sockerrno
, tcp_sockerrno
;
380 struct rta_host
*host
;
382 setlocale (LC_ALL
, "");
383 bindtextdomain (PACKAGE
, LOCALEDIR
);
384 textdomain (PACKAGE
);
386 /* print a helpful error message if geteuid != 0 */
387 np_warn_if_not_root();
389 /* we only need to be setsuid when we get the sockets, so do
390 * that before pointer magic (esp. on network data) */
391 icmp_sockerrno
= udp_sockerrno
= tcp_sockerrno
= sockets
= 0;
393 if((icmp_sock
= socket(PF_INET
, SOCK_RAW
, IPPROTO_ICMP
)) != -1)
394 sockets
|= HAVE_ICMP
;
395 else icmp_sockerrno
= errno
;
397 /* if((udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1) */
398 /* sockets |= HAVE_UDP; */
399 /* else udp_sockerrno = errno; */
401 /* if((tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) != -1) */
402 /* sockets |= HAVE_TCP; */
403 /* else tcp_sockerrno = errno; */
405 /* now drop privileges (no effect if not setsuid or geteuid() == 0) */
408 /* POSIXLY_CORRECT might break things, so unset it (the portable way) */
411 /* use the pid to mark packets as ours */
412 /* Some systems have 32-bit pid_t so mask off only 16 bits */
413 pid
= getpid() & 0xffff;
414 /* printf("pid = %u\n", pid); */
416 /* get calling name the old-fashioned way for portability instead
417 * of relying on the glibc-ism __progname */
418 ptr
= strrchr(argv
[0], '/');
419 if(ptr
) progname
= &ptr
[1];
420 else progname
= argv
[0];
422 /* now set defaults. Use progname to set them initially (allows for
423 * superfast check_host program when target host is up */
424 cursor
= list
= NULL
;
432 protocols
= HAVE_ICMP
| HAVE_UDP
| HAVE_TCP
;
433 pkt_interval
= 80000; /* 80 msec packet interval by default */
436 if(!strcmp(progname
, "check_icmp") || !strcmp(progname
, "check_ping")) {
438 protocols
= HAVE_ICMP
;
440 else if(!strcmp(progname
, "check_host")) {
441 mode
= MODE_HOSTCHECK
;
442 pkt_interval
= 1000000;
444 crit
.rta
= warn
.rta
= 1000000;
445 crit
.pl
= warn
.pl
= 100;
447 else if(!strcmp(progname
, "check_rta_multi")) {
450 pkt_interval
= 50000;
454 /* Parse extra opts if any */
455 argv
=np_extra_opts(&argc
, argv
, progname
);
457 /* support "--help" and "--version" */
459 if(!strcmp(argv
[1], "--help"))
460 strcpy(argv
[1], "-h");
461 if(!strcmp(argv
[1], "--version"))
462 strcpy(argv
[1], "-V");
465 /* parse the arguments */
466 for(i
= 1; i
< argc
; i
++) {
467 while((arg
= getopt(argc
, argv
, "vhVw:c:n:p:t:H:s:i:b:I:l:m:")) != EOF
) {
474 size
= strtol(optarg
,NULL
,0);
475 if (size
>= (sizeof(struct icmp
) + sizeof(struct icmp_ping_data
)) &&
476 size
< MAX_PING_DATA
) {
477 icmp_data_size
= size
;
478 icmp_pkt_size
= size
+ ICMP_MINLEN
;
480 usage_va("ICMP data length must be between: %d and %d",
481 sizeof(struct icmp
) + sizeof(struct icmp_ping_data
),
486 pkt_interval
= get_timevar(optarg
);
489 target_interval
= get_timevar(optarg
);
492 get_threshold(optarg
, &warn
);
495 get_threshold(optarg
, &crit
);
499 packets
= strtoul(optarg
, NULL
, 0);
502 timeout
= strtoul(optarg
, NULL
, 0);
503 if(!timeout
) timeout
= 10;
509 ttl
= (unsigned char)strtoul(optarg
, NULL
, 0);
512 min_hosts_alive
= (int)strtoul(optarg
, NULL
, 0);
514 case 'd': /* implement later, for cluster checks */
515 warn_down
= (unsigned char)strtoul(optarg
, &ptr
, 0);
517 crit_down
= (unsigned char)strtoul(ptr
+ 1, NULL
, 0);
520 case 's': /* specify source IP address */
521 set_source_ip(optarg
);
523 case 'V': /* version */
524 print_revision (progname
, NP_VERSION
);
533 argv
= &argv
[optind
];
540 crash("No hosts to check");
545 if(icmp_sock
== -1) {
546 errno
= icmp_sockerrno
;
547 crash("Failed to obtain ICMP socket");
550 /* if(udp_sock == -1) { */
551 /* errno = icmp_sockerrno; */
552 /* crash("Failed to obtain UDP socket"); */
555 /* if(tcp_sock == -1) { */
556 /* errno = icmp_sockerrno; */
557 /* crash("Failed to obtain TCP socker"); */
564 result
= setsockopt(icmp_sock
, SOL_IP
, IP_TTL
, &ttl
, sizeof(ttl
));
566 if(result
== -1) printf("setsockopt failed\n");
567 else printf("ttl set to %u\n", ttl
);
571 /* stupid users should be able to give whatever thresholds they want
572 * (nothing will break if they do), but some anal plugin maintainer
573 * will probably add some printf() thing here later, so it might be
574 * best to at least show them where to do it. ;) */
575 if(warn
.pl
> crit
.pl
) warn
.pl
= crit
.pl
;
576 if(warn
.rta
> crit
.rta
) warn
.rta
= crit
.rta
;
577 if(warn_down
> crit_down
) crit_down
= warn_down
;
579 signal(SIGINT
, finish
);
580 signal(SIGHUP
, finish
);
581 signal(SIGTERM
, finish
);
582 signal(SIGALRM
, finish
);
583 if(debug
) printf("Setting alarm timeout to %u seconds\n", timeout
);
586 /* make sure we don't wait any longer than necessary */
587 gettimeofday(&prog_start
, &tz
);
588 max_completion_time
=
589 ((targets
* packets
* pkt_interval
) + (targets
* target_interval
)) +
590 (targets
* packets
* crit
.rta
) + crit
.rta
;
593 printf("packets: %u, targets: %u\n"
594 "target_interval: %0.3f, pkt_interval %0.3f\n"
596 "max_completion_time: %0.3f\n",
598 (float)target_interval
/ 1000, (float)pkt_interval
/ 1000,
599 (float)crit
.rta
/ 1000,
600 (float)max_completion_time
/ 1000);
604 if(max_completion_time
> (u_int
)timeout
* 1000000) {
605 printf("max_completion_time: %llu timeout: %u\n",
606 max_completion_time
, timeout
);
607 printf("Timout must be at lest %llu\n",
608 max_completion_time
/ 1000000 + 1);
613 printf("crit = {%u, %u%%}, warn = {%u, %u%%}\n",
614 crit
.rta
, crit
.pl
, warn
.rta
, warn
.pl
);
615 printf("pkt_interval: %u target_interval: %u retry_interval: %u\n",
616 pkt_interval
, target_interval
, retry_interval
);
617 printf("icmp_pkt_size: %u timeout: %u\n",
618 icmp_pkt_size
, timeout
);
623 crash("packets is > 20 (%d)", packets
);
626 if(min_hosts_alive
< -1) {
628 crash("minimum alive hosts is negative (%i)", min_hosts_alive
);
632 table
= malloc(sizeof(struct rta_host
**) * targets
);
635 host
->id
= i
*packets
;
653 u_int final_wait
, time_passed
;
655 /* this loop might actually violate the pkt_interval or target_interval
656 * settings, but only if there aren't any packets on the wire which
657 * indicates that the target can handle an increased packet rate */
658 for(i
= 0; i
< packets
; i
++) {
659 for(t
= 0; t
< targets
; t
++) {
660 /* don't send useless packets */
661 if(!targets_alive
) finish(0);
662 if(table
[t
]->flags
& FLAG_LOST_CAUSE
) {
663 if(debug
) printf("%s is a lost cause. not sending any more\n",
668 /* we're still in the game, so send next packet */
669 (void)send_icmp_ping(icmp_sock
, table
[t
]);
670 result
= wait_for_reply(icmp_sock
, target_interval
);
672 result
= wait_for_reply(icmp_sock
, pkt_interval
* targets
);
675 if(icmp_pkts_en_route
&& targets_alive
) {
676 time_passed
= get_timevaldiff(NULL
, NULL
);
677 final_wait
= max_completion_time
- time_passed
;
680 printf("time_passed: %u final_wait: %u max_completion_time: %llu\n",
681 time_passed
, final_wait
, max_completion_time
);
683 if(time_passed
> max_completion_time
) {
684 if(debug
) printf("Time passed. Finishing up\n");
688 /* catch the packets that might come in within the timeframe, but
690 if(debug
) printf("Waiting for %u micro-seconds (%0.3f msecs)\n",
691 final_wait
, (float)final_wait
/ 1000);
692 result
= wait_for_reply(icmp_sock
, final_wait
);
696 /* response structure:
697 * ip header : 20 bytes
698 * icmp header : 28 bytes
699 * icmp echo reply : the rest
702 wait_for_reply(int sock
, u_int t
)
705 static unsigned char buf
[4096];
706 struct sockaddr_in resp_addr
;
709 struct rta_host
*host
;
710 struct icmp_ping_data data
;
711 struct timeval wait_start
, now
;
712 u_int tdiff
, i
, per_pkt_wait
;
714 /* if we can't listen or don't have anything to listen to, just return */
715 if(!t
|| !icmp_pkts_en_route
) return 0;
717 gettimeofday(&wait_start
, &tz
);
720 per_pkt_wait
= t
/ icmp_pkts_en_route
;
721 while(icmp_pkts_en_route
&& get_timevaldiff(&wait_start
, NULL
) < i
) {
724 /* wrap up if all targets are declared dead */
726 get_timevaldiff(&prog_start
, NULL
) >= max_completion_time
||
727 (mode
== MODE_HOSTCHECK
&& targets_down
))
732 /* reap responses until we hit a timeout */
733 n
= recvfrom_wto(sock
, buf
, sizeof(buf
),
734 (struct sockaddr
*)&resp_addr
, &t
);
737 printf("recvfrom_wto() timed out during a %u usecs wait\n",
740 continue; /* timeout for this one, so keep trying */
743 if(debug
) printf("recvfrom_wto() returned errors\n");
747 ip
= (struct ip
*)buf
;
748 if(debug
> 1) printf("received %u bytes from %s\n",
749 ntohs(ip
->ip_len
), inet_ntoa(resp_addr
.sin_addr
));
751 /* obsolete. alpha on tru64 provides the necessary defines, but isn't broken */
752 /* #if defined( __alpha__ ) && __STDC__ && !defined( __GLIBC__ ) */
753 /* alpha headers are decidedly broken. Using an ansi compiler,
754 * they provide ip_vhl instead of ip_hl and ip_v, so we mask
755 * off the bottom 4 bits */
756 /* hlen = (ip->ip_vhl & 0x0f) << 2; */
758 hlen
= ip
->ip_hl
<< 2;
761 if(n
< (hlen
+ ICMP_MINLEN
)) {
762 crash("received packet too short for ICMP (%d bytes, expected %d) from %s\n",
763 n
, hlen
+ icmp_pkt_size
, inet_ntoa(resp_addr
.sin_addr
));
765 /* else if(debug) { */
766 /* printf("ip header size: %u, packet size: %u (expected %u, %u)\n", */
767 /* hlen, ntohs(ip->ip_len) - hlen, */
768 /* sizeof(struct ip), icmp_pkt_size); */
771 /* check the response */
772 memcpy(&icp
, buf
+ hlen
, sizeof(icp
));
774 if(ntohs(icp
.icmp_id
) != pid
|| icp
.icmp_type
!= ICMP_ECHOREPLY
||
775 ntohs(icp
.icmp_seq
) >= targets
*packets
) {
776 if(debug
> 2) printf("not a proper ICMP_ECHOREPLY\n");
777 handle_random_icmp(buf
+ hlen
, &resp_addr
);
781 /* this is indeed a valid response */
782 memcpy(&data
, icp
.icmp_data
, sizeof(data
));
784 printf("ICMP echo-reply of len %u, id %u, seq %u, cksum 0x%X\n",
785 sizeof(data
), ntohs(icp
.icmp_id
), ntohs(icp
.icmp_seq
), icp
.icmp_cksum
);
787 host
= table
[ntohs(icp
.icmp_seq
)/packets
];
788 gettimeofday(&now
, &tz
);
789 tdiff
= get_timevaldiff(&data
.stime
, &now
);
791 host
->time_waited
+= tdiff
;
794 if (tdiff
> host
->rtmax
)
796 if (tdiff
< host
->rtmin
)
800 printf("%0.3f ms rtt from %s, outgoing ttl: %u, incoming ttl: %u, max: %0.3f, min: %0.3f\n",
801 (float)tdiff
/ 1000, inet_ntoa(resp_addr
.sin_addr
),
802 ttl
, ip
->ip_ttl
, (float)host
->rtmax
/ 1000, (float)host
->rtmin
/ 1000);
805 /* if we're in hostcheck mode, exit with limited printouts */
806 if(mode
== MODE_HOSTCHECK
) {
807 printf("OK - %s responds to ICMP. Packet %u, rta %0.3fms|"
808 "pkt=%u;;0;%u rta=%0.3f;%0.3f;%0.3f;;\n",
809 host
->name
, icmp_recv
, (float)tdiff
/ 1000,
810 icmp_recv
, packets
, (float)tdiff
/ 1000,
811 (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000);
819 /* the ping functions */
821 send_icmp_ping(int sock
, struct rta_host
*host
)
824 void *buf
; /* re-use so we prevent leaks */
829 struct icmp_ping_data data
;
831 struct sockaddr
*addr
;
835 crash("Attempt to send on bogus socket");
838 addr
= (struct sockaddr
*)&host
->saddr_in
;
841 if (!(packet
.buf
= malloc(icmp_pkt_size
))) {
842 crash("send_icmp_ping(): failed to malloc %d bytes for send buffer",
844 return -1; /* might be reached if we're in debug mode */
847 memset(packet
.buf
, 0, icmp_pkt_size
);
849 if((gettimeofday(&tv
, &tz
)) == -1) return -1;
851 data
.ping_id
= 10; /* host->icmp.icmp_sent; */
852 memcpy(&data
.stime
, &tv
, sizeof(tv
));
853 memcpy(&packet
.icp
->icmp_data
, &data
, sizeof(data
));
854 packet
.icp
->icmp_type
= ICMP_ECHO
;
855 packet
.icp
->icmp_code
= 0;
856 packet
.icp
->icmp_cksum
= 0;
857 packet
.icp
->icmp_id
= htons(pid
);
858 packet
.icp
->icmp_seq
= htons(host
->id
++);
859 packet
.icp
->icmp_cksum
= icmp_checksum(packet
.cksum_in
, icmp_pkt_size
);
862 printf("Sending ICMP echo-request of len %u, id %u, seq %u, cksum 0x%X to host %s\n",
863 sizeof(data
), ntohs(packet
.icp
->icmp_id
), ntohs(packet
.icp
->icmp_seq
), packet
.icp
->icmp_cksum
, host
->name
);
865 len
= sendto(sock
, packet
.buf
, icmp_pkt_size
, 0, (struct sockaddr
*)addr
,
866 sizeof(struct sockaddr
));
868 if(len
< 0 || (unsigned int)len
!= icmp_pkt_size
) {
869 if(debug
) printf("Failed to send ping to %s\n",
870 inet_ntoa(host
->saddr_in
.sin_addr
));
881 recvfrom_wto(int sock
, void *buf
, unsigned int len
, struct sockaddr
*saddr
,
886 struct timeval to
, then
, now
;
890 if(debug
) printf("*timo is not\n");
894 to
.tv_sec
= *timo
/ 1000000;
895 to
.tv_usec
= (*timo
- (to
.tv_sec
* 1000000));
901 gettimeofday(&then
, &tz
);
902 n
= select(sock
+ 1, &rd
, &wr
, NULL
, &to
);
903 if(n
< 0) crash("select() in recvfrom_wto");
904 gettimeofday(&now
, &tz
);
905 *timo
= get_timevaldiff(&then
, &now
);
907 if(!n
) return 0; /* timeout */
909 slen
= sizeof(struct sockaddr
);
911 return recvfrom(sock
, buf
, len
, 0, saddr
, &slen
);
920 struct rta_host
*host
;
921 const char *status_string
[] =
922 {"OK", "WARNING", "CRITICAL", "UNKNOWN", "DEPENDENT"};
927 if(debug
> 1) printf("finish(%d) called\n", sig
);
929 if(icmp_sock
!= -1) close(icmp_sock
);
930 if(udp_sock
!= -1) close(udp_sock
);
931 if(tcp_sock
!= -1) close(tcp_sock
);
934 printf("icmp_sent: %u icmp_recv: %u icmp_lost: %u\n",
935 icmp_sent
, icmp_recv
, icmp_lost
);
936 printf("targets: %u targets_alive: %u\n", targets
, targets_alive
);
939 /* iterate thrice to calculate values, give output, and print perfparse */
942 if(!host
->icmp_recv
) {
943 /* rta 0 is ofcourse not entirely correct, but will still show up
944 * conspicuosly as missing entries in perfparse and cacti */
947 status
= STATE_CRITICAL
;
948 /* up the down counter if not already counted */
949 if(!(host
->flags
& FLAG_LOST_CAUSE
) && targets_alive
) targets_down
++;
952 pl
= ((host
->icmp_sent
- host
->icmp_recv
) * 100) / host
->icmp_sent
;
953 rta
= (double)host
->time_waited
/ host
->icmp_recv
;
957 if(pl
>= crit
.pl
|| rta
>= crit
.rta
) {
958 status
= STATE_CRITICAL
;
960 else if(!status
&& (pl
>= warn
.pl
|| rta
>= warn
.rta
)) {
961 status
= STATE_WARNING
;
970 /* this is inevitable */
971 if(!targets_alive
) status
= STATE_CRITICAL
;
972 if(min_hosts_alive
> -1) {
973 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
974 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
976 printf("%s - ", status_string
[status
]);
982 if(i
< targets
) printf(" :: ");
986 if(!host
->icmp_recv
) {
987 status
= STATE_CRITICAL
;
988 if(host
->flags
& FLAG_LOST_CAUSE
) {
989 printf("%s: %s @ %s. rta nan, lost %d%%",
991 get_icmp_error_msg(host
->icmp_type
, host
->icmp_code
),
992 inet_ntoa(host
->error_addr
),
995 else { /* not marked as lost cause, so we have no flags for it */
996 printf("%s: rta nan, lost 100%%", host
->name
);
999 else { /* !icmp_recv */
1000 printf("%s: rta %0.3fms, lost %u%%",
1001 host
->name
, host
->rta
/ 1000, host
->pl
);
1007 /* iterate once more for pretty perfparse output */
1013 printf("%srta=%0.3fms;%0.3f;%0.3f;0; %spl=%u%%;%u;%u;; %srtmax=%0.3fms;;;; %srtmin=%0.3fms;;;; ",
1014 (targets
> 1) ? host
->name
: "",
1015 host
->rta
/ 1000, (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000,
1016 (targets
> 1) ? host
->name
: "", host
->pl
, warn
.pl
, crit
.pl
,
1017 (targets
> 1) ? host
->name
: "", (float)host
->rtmax
/ 1000,
1018 (targets
> 1) ? host
->name
: "", (host
->rtmin
< DBL_MAX
) ? (float)host
->rtmin
/ 1000 : (float)0);
1023 if(min_hosts_alive
> -1) {
1024 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
1025 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
1028 /* finish with an empty line */
1030 if(debug
) printf("targets: %u, targets_alive: %u, hosts_ok: %u, hosts_warn: %u, min_hosts_alive: %i\n",
1031 targets
, targets_alive
, hosts_ok
, hosts_warn
, min_hosts_alive
);
1037 get_timevaldiff(struct timeval
*early
, struct timeval
*later
)
1043 gettimeofday(&now
, &tz
);
1046 if(!early
) early
= &prog_start
;
1048 /* if early > later we return 0 so as to indicate a timeout */
1049 if(early
->tv_sec
> later
->tv_sec
||
1050 (early
->tv_sec
== later
->tv_sec
&& early
->tv_usec
> later
->tv_usec
))
1055 ret
= (later
->tv_sec
- early
->tv_sec
) * 1000000;
1056 ret
+= later
->tv_usec
- early
->tv_usec
;
1062 add_target_ip(char *arg
, struct in_addr
*in
)
1064 struct rta_host
*host
;
1066 /* disregard obviously stupid addresses */
1067 if(in
->s_addr
== INADDR_NONE
|| in
->s_addr
== INADDR_ANY
)
1070 /* no point in adding two identical IP's, so don't. ;) */
1073 if(host
->saddr_in
.sin_addr
.s_addr
== in
->s_addr
) {
1074 if(debug
) printf("Identical IP already exists. Not adding %s\n", arg
);
1080 /* add the fresh ip */
1081 host
= malloc(sizeof(struct rta_host
));
1083 crash("add_target_ip(%s, %s): malloc(%d) failed",
1084 arg
, inet_ntoa(*in
), sizeof(struct rta_host
));
1086 memset(host
, 0, sizeof(struct rta_host
));
1088 /* set the values. use calling name for output */
1089 host
->name
= strdup(arg
);
1091 /* fill out the sockaddr_in struct */
1092 host
->saddr_in
.sin_family
= AF_INET
;
1093 host
->saddr_in
.sin_addr
.s_addr
= in
->s_addr
;
1095 host
->rtmin
= DBL_MAX
;
1097 if(!list
) list
= cursor
= host
;
1098 else cursor
->next
= host
;
1106 /* wrapper for add_target_ip */
1108 add_target(char *arg
)
1112 struct in_addr
*in
, ip
;
1114 /* don't resolve if we don't have to */
1115 if((ip
.s_addr
= inet_addr(arg
)) != INADDR_NONE
) {
1116 /* don't add all ip's if we were given a specific one */
1117 return add_target_ip(arg
, &ip
);
1118 /* he = gethostbyaddr((char *)in, sizeof(struct in_addr), AF_INET); */
1119 /* if(!he) return add_target_ip(arg, in); */
1123 he
= gethostbyname(arg
);
1126 crash("Failed to resolve %s", arg
);
1131 /* possibly add all the IP's as targets */
1132 for(i
= 0; he
->h_addr_list
[i
]; i
++) {
1133 in
= (struct in_addr
*)he
->h_addr_list
[i
];
1134 add_target_ip(arg
, in
);
1136 /* this is silly, but it works */
1137 if(mode
== MODE_HOSTCHECK
|| mode
== MODE_ALL
) {
1138 if(debug
> 2) printf("mode: %d\n", mode
);
1148 set_source_ip(char *arg
)
1150 struct sockaddr_in src
;
1152 memset(&src
, 0, sizeof(src
));
1153 src
.sin_family
= AF_INET
;
1154 if((src
.sin_addr
.s_addr
= inet_addr(arg
)) == INADDR_NONE
)
1155 src
.sin_addr
.s_addr
= get_ip_address(arg
);
1156 if(bind(icmp_sock
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
1157 crash("Cannot bind to IP address %s", arg
);
1160 /* TODO: Move this to netutils.c and also change check_dhcp to use that. */
1162 get_ip_address(const char *ifname
)
1164 #if defined(SIOCGIFADDR)
1166 struct sockaddr_in ip
;
1168 strncpy(ifr
.ifr_name
, ifname
, sizeof(ifr
.ifr_name
) - 1);
1169 ifr
.ifr_name
[sizeof(ifr
.ifr_name
) - 1] = '\0';
1170 if(ioctl(icmp_sock
, SIOCGIFADDR
, &ifr
) == -1)
1171 crash("Cannot determine IP address of interface %s", ifname
);
1172 memcpy(&ip
, &ifr
.ifr_addr
, sizeof(ip
));
1173 return ip
.sin_addr
.s_addr
;
1176 crash("Cannot get interface IP address on this platform.");
1184 * return value is in microseconds
1187 get_timevar(const char *str
)
1191 u_int i
, d
; /* integer and decimal, respectively */
1192 u_int factor
= 1000; /* default to milliseconds */
1198 /* unit might be given as ms|m (millisec),
1199 * us|u (microsec) or just plain s, for seconds */
1202 if(len
>= 2 && !isdigit((int)str
[len
- 2])) p
= str
[len
- 2];
1203 if(p
&& u
== 's') u
= p
;
1205 if(debug
> 2) printf("evaluating %s, u: %c, p: %c\n", str
, u
, p
);
1207 if(u
== 'u') factor
= 1; /* microseconds */
1208 else if(u
== 'm') factor
= 1000; /* milliseconds */
1209 else if(u
== 's') factor
= 1000000; /* seconds */
1210 if(debug
> 2) printf("factor is %u\n", factor
);
1212 i
= strtoul(str
, &ptr
, 0);
1213 if(!ptr
|| *ptr
!= '.' || strlen(ptr
) < 2 || factor
== 1)
1216 /* time specified in usecs can't have decimal points, so ignore them */
1217 if(factor
== 1) return i
;
1219 d
= strtoul(ptr
+ 1, NULL
, 0);
1221 /* d is decimal, so get rid of excess digits */
1222 while(d
>= factor
) d
/= 10;
1224 /* the last parenthesis avoids floating point exceptions. */
1225 return ((i
* factor
) + (d
* (factor
/ 10)));
1228 /* not too good at checking errors, but it'll do (main() should barfe on -1) */
1230 get_threshold(char *str
, threshold
*th
)
1232 char *p
= NULL
, i
= 0;
1234 if(!str
|| !strlen(str
) || !th
) return -1;
1236 /* pointer magic slims code by 10 lines. i is bof-stop on stupid libc's */
1237 p
= &str
[strlen(str
) - 1];
1238 while(p
!= &str
[1]) {
1239 if(*p
== '%') *p
= '\0';
1240 else if(*p
== ',' && i
) {
1241 *p
= '\0'; /* reset it so get_timevar(str) works nicely later */
1242 th
->pl
= (unsigned char)strtoul(p
+1, NULL
, 0);
1248 th
->rta
= get_timevar(str
);
1250 if(!th
->rta
) return -1;
1252 if(th
->rta
> MAXTTL
* 1000000) th
->rta
= MAXTTL
* 1000000;
1253 if(th
->pl
> 100) th
->pl
= 100;
1259 icmp_checksum(unsigned short *p
, int n
)
1261 register unsigned short cksum
;
1262 register long sum
= 0;
1269 /* mop up the occasional odd byte */
1270 if(n
== 1) sum
+= (unsigned char)*p
;
1272 sum
= (sum
>> 16) + (sum
& 0xffff); /* add hi 16 to low 16 */
1273 sum
+= (sum
>> 16); /* add carry */
1274 cksum
= ~sum
; /* ones-complement, trunc to 16 bits */
1283 /*print_revision (progname);*/ /* FIXME: Why? */
1285 printf ("Copyright (c) 2005 Andreas Ericsson <ae@op5.se>\n");
1286 printf (COPYRIGHT
, copyright
, email
);
1292 printf (UT_HELP_VRSN
);
1293 printf (UT_EXTRA_OPTS
);
1295 printf (" %s\n", "-H");
1296 printf (" %s\n", _("specify a target"));
1297 printf (" %s\n", "-w");
1298 printf (" %s", _("warning threshold (currently "));
1299 printf ("%0.3fms,%u%%)\n", (float)warn
.rta
/ 1000, warn
.pl
);
1300 printf (" %s\n", "-c");
1301 printf (" %s", _("critical threshold (currently "));
1302 printf ("%0.3fms,%u%%)\n", (float)crit
.rta
/ 1000, crit
.pl
);
1303 printf (" %s\n", "-s");
1304 printf (" %s\n", _("specify a source IP address or device name"));
1305 printf (" %s\n", "-n");
1306 printf (" %s", _("number of packets to send (currently "));
1307 printf ("%u)\n",packets
);
1308 printf (" %s\n", "-i");
1309 printf (" %s", _("max packet interval (currently "));
1310 printf ("%0.3fms)\n",(float)pkt_interval
/ 1000);
1311 printf (" %s\n", "-I");
1312 printf (" %s", _("max target interval (currently "));
1313 printf ("%0.3fms)\n", (float)target_interval
/ 1000);
1314 printf (" %s\n", "-m");
1315 printf (" %s",_("number of alive hosts required for success"));
1317 printf (" %s\n", "-l");
1318 printf (" %s", _("TTL on outgoing packets (currently "));
1319 printf ("%u)\n", ttl
);
1320 printf (" %s\n", "-t");
1321 printf (" %s",_("timeout value (seconds, currently "));
1322 printf ("%u)\n", timeout
);
1323 printf (" %s\n", "-b");
1324 printf (" %s\n", _("Number of icmp data bytes to send"));
1325 printf (" %s %u + %d)\n", _("Packet size will be data bytes + icmp header (currently"),icmp_data_size
, ICMP_MINLEN
);
1326 printf (" %s\n", "-v");
1327 printf (" %s\n", _("verbose"));
1330 printf ("%s\n", _("Notes:"));
1331 printf (" %s\n", _("The -H switch is optional. Naming a host (or several) to check is not."));
1333 printf (" %s\n", _("Threshold format for -w and -c is 200.25,60% for 200.25 msec RTA and 60%"));
1334 printf (" %s\n", _("packet loss. The default values should work well for most users."));
1335 printf (" %s\n", _("You can specify different RTA factors using the standardized abbreviations"));
1336 printf (" %s\n", _("us (microseconds), ms (milliseconds, default) or just plain s for seconds."));
1337 /* -d not yet implemented */
1338 /* printf ("%s\n", _("Threshold format for -d is warn,crit. 12,14 means WARNING if >= 12 hops"));
1339 printf ("%s\n", _("are spent and CRITICAL if >= 14 hops are spent."));
1340 printf ("%s\n\n", _("NOTE: Some systems decrease TTL when forming ICMP_ECHOREPLY, others do not."));*/
1342 printf (" %s\n", _("The -v switch can be specified several times for increased verbosity."));
1343 /* printf ("%s\n", _("Long options are currently unsupported."));
1344 printf ("%s\n", _("Options marked with * require an argument"));
1347 printf (UT_SUPPORT
);
1355 printf ("%s\n", _("Usage:"));
1356 printf(" %s [options] [-H] host1 host2 hostN\n", progname
);