1 /*****************************************************************************
3 * Monitoring check_icmp plugin
6 * Copyright (c) 2005-2008 Monitoring 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
= "devel@monitoring-plugins.org";
45 /** Monitoring 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
*, struct timeval
*);
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
;
385 setlocale (LC_ALL
, "");
386 bindtextdomain (PACKAGE
, LOCALEDIR
);
387 textdomain (PACKAGE
);
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) */
409 if(setsockopt(icmp_sock
, SOL_SOCKET
, SO_TIMESTAMP
, &on
, sizeof(on
)))
410 if(debug
) printf("Warning: no SO_TIMESTAMP support\n");
411 #endif // SO_TIMESTAMP
413 /* POSIXLY_CORRECT might break things, so unset it (the portable way) */
416 /* use the pid to mark packets as ours */
417 /* Some systems have 32-bit pid_t so mask off only 16 bits */
418 pid
= getpid() & 0xffff;
419 /* printf("pid = %u\n", pid); */
421 /* get calling name the old-fashioned way for portability instead
422 * of relying on the glibc-ism __progname */
423 ptr
= strrchr(argv
[0], '/');
424 if(ptr
) progname
= &ptr
[1];
425 else progname
= argv
[0];
427 /* now set defaults. Use progname to set them initially (allows for
428 * superfast check_host program when target host is up */
429 cursor
= list
= NULL
;
437 protocols
= HAVE_ICMP
| HAVE_UDP
| HAVE_TCP
;
438 pkt_interval
= 80000; /* 80 msec packet interval by default */
441 if(!strcmp(progname
, "check_icmp") || !strcmp(progname
, "check_ping")) {
443 protocols
= HAVE_ICMP
;
445 else if(!strcmp(progname
, "check_host")) {
446 mode
= MODE_HOSTCHECK
;
447 pkt_interval
= 1000000;
449 crit
.rta
= warn
.rta
= 1000000;
450 crit
.pl
= warn
.pl
= 100;
452 else if(!strcmp(progname
, "check_rta_multi")) {
455 pkt_interval
= 50000;
459 /* Parse extra opts if any */
460 argv
=np_extra_opts(&argc
, argv
, progname
);
462 /* support "--help" and "--version" */
464 if(!strcmp(argv
[1], "--help"))
465 strcpy(argv
[1], "-h");
466 if(!strcmp(argv
[1], "--version"))
467 strcpy(argv
[1], "-V");
470 /* parse the arguments */
471 for(i
= 1; i
< argc
; i
++) {
472 while((arg
= getopt(argc
, argv
, "vhVw:c:n:p:t:H:s:i:b:I:l:m:")) != EOF
) {
479 size
= (unsigned short)strtol(optarg
,NULL
,0);
480 if (size
>= (sizeof(struct icmp
) + sizeof(struct icmp_ping_data
)) &&
481 size
< MAX_PING_DATA
) {
482 icmp_data_size
= size
;
483 icmp_pkt_size
= size
+ ICMP_MINLEN
;
485 usage_va("ICMP data length must be between: %d and %d",
486 sizeof(struct icmp
) + sizeof(struct icmp_ping_data
),
490 pkt_interval
= get_timevar(optarg
);
493 target_interval
= get_timevar(optarg
);
496 get_threshold(optarg
, &warn
);
499 get_threshold(optarg
, &crit
);
503 packets
= strtoul(optarg
, NULL
, 0);
506 timeout
= strtoul(optarg
, NULL
, 0);
507 if(!timeout
) timeout
= 10;
513 ttl
= (unsigned char)strtoul(optarg
, NULL
, 0);
516 min_hosts_alive
= (int)strtoul(optarg
, NULL
, 0);
518 case 'd': /* implement later, for cluster checks */
519 warn_down
= (unsigned char)strtoul(optarg
, &ptr
, 0);
521 crit_down
= (unsigned char)strtoul(ptr
+ 1, NULL
, 0);
524 case 's': /* specify source IP address */
525 set_source_ip(optarg
);
527 case 'V': /* version */
528 print_revision (progname
, NP_VERSION
);
529 exit (STATE_UNKNOWN
);
532 exit (STATE_UNKNOWN
);
537 argv
= &argv
[optind
];
544 crash("No hosts to check");
549 if(icmp_sock
== -1) {
550 errno
= icmp_sockerrno
;
551 crash("Failed to obtain ICMP socket");
554 /* if(udp_sock == -1) { */
555 /* errno = icmp_sockerrno; */
556 /* crash("Failed to obtain UDP socket"); */
559 /* if(tcp_sock == -1) { */
560 /* errno = icmp_sockerrno; */
561 /* crash("Failed to obtain TCP socker"); */
568 result
= setsockopt(icmp_sock
, SOL_IP
, IP_TTL
, &ttl
, sizeof(ttl
));
570 if(result
== -1) printf("setsockopt failed\n");
571 else printf("ttl set to %u\n", ttl
);
575 /* stupid users should be able to give whatever thresholds they want
576 * (nothing will break if they do), but some anal plugin maintainer
577 * will probably add some printf() thing here later, so it might be
578 * best to at least show them where to do it. ;) */
579 if(warn
.pl
> crit
.pl
) warn
.pl
= crit
.pl
;
580 if(warn
.rta
> crit
.rta
) warn
.rta
= crit
.rta
;
581 if(warn_down
> crit_down
) crit_down
= warn_down
;
583 signal(SIGINT
, finish
);
584 signal(SIGHUP
, finish
);
585 signal(SIGTERM
, finish
);
586 signal(SIGALRM
, finish
);
587 if(debug
) printf("Setting alarm timeout to %u seconds\n", timeout
);
590 /* make sure we don't wait any longer than necessary */
591 gettimeofday(&prog_start
, &tz
);
592 max_completion_time
=
593 ((targets
* packets
* pkt_interval
) + (targets
* target_interval
)) +
594 (targets
* packets
* crit
.rta
) + crit
.rta
;
597 printf("packets: %u, targets: %u\n"
598 "target_interval: %0.3f, pkt_interval %0.3f\n"
600 "max_completion_time: %0.3f\n",
602 (float)target_interval
/ 1000, (float)pkt_interval
/ 1000,
603 (float)crit
.rta
/ 1000,
604 (float)max_completion_time
/ 1000);
608 if(max_completion_time
> (u_int
)timeout
* 1000000) {
609 printf("max_completion_time: %llu timeout: %u\n",
610 max_completion_time
, timeout
);
611 printf("Timout must be at lest %llu\n",
612 max_completion_time
/ 1000000 + 1);
617 printf("crit = {%u, %u%%}, warn = {%u, %u%%}\n",
618 crit
.rta
, crit
.pl
, warn
.rta
, warn
.pl
);
619 printf("pkt_interval: %u target_interval: %u retry_interval: %u\n",
620 pkt_interval
, target_interval
, retry_interval
);
621 printf("icmp_pkt_size: %u timeout: %u\n",
622 icmp_pkt_size
, timeout
);
627 crash("packets is > 20 (%d)", packets
);
630 if(min_hosts_alive
< -1) {
632 crash("minimum alive hosts is negative (%i)", min_hosts_alive
);
636 table
= malloc(sizeof(struct rta_host
**) * targets
);
639 host
->id
= i
*packets
;
657 u_int final_wait
, time_passed
;
659 /* this loop might actually violate the pkt_interval or target_interval
660 * settings, but only if there aren't any packets on the wire which
661 * indicates that the target can handle an increased packet rate */
662 for(i
= 0; i
< packets
; i
++) {
663 for(t
= 0; t
< targets
; t
++) {
664 /* don't send useless packets */
665 if(!targets_alive
) finish(0);
666 if(table
[t
]->flags
& FLAG_LOST_CAUSE
) {
667 if(debug
) printf("%s is a lost cause. not sending any more\n",
672 /* we're still in the game, so send next packet */
673 (void)send_icmp_ping(icmp_sock
, table
[t
]);
674 result
= wait_for_reply(icmp_sock
, target_interval
);
676 result
= wait_for_reply(icmp_sock
, pkt_interval
* targets
);
679 if(icmp_pkts_en_route
&& targets_alive
) {
680 time_passed
= get_timevaldiff(NULL
, NULL
);
681 final_wait
= max_completion_time
- time_passed
;
684 printf("time_passed: %u final_wait: %u max_completion_time: %llu\n",
685 time_passed
, final_wait
, max_completion_time
);
687 if(time_passed
> max_completion_time
) {
688 if(debug
) printf("Time passed. Finishing up\n");
692 /* catch the packets that might come in within the timeframe, but
694 if(debug
) printf("Waiting for %u micro-seconds (%0.3f msecs)\n",
695 final_wait
, (float)final_wait
/ 1000);
696 result
= wait_for_reply(icmp_sock
, final_wait
);
700 /* response structure:
701 * ip header : 20 bytes
702 * icmp header : 28 bytes
703 * icmp echo reply : the rest
706 wait_for_reply(int sock
, u_int t
)
709 static unsigned char buf
[4096];
710 struct sockaddr_in resp_addr
;
713 struct rta_host
*host
;
714 struct icmp_ping_data data
;
715 struct timeval wait_start
, now
;
716 u_int tdiff
, i
, per_pkt_wait
;
718 /* if we can't listen or don't have anything to listen to, just return */
719 if(!t
|| !icmp_pkts_en_route
) return 0;
721 gettimeofday(&wait_start
, &tz
);
724 per_pkt_wait
= t
/ icmp_pkts_en_route
;
725 while(icmp_pkts_en_route
&& get_timevaldiff(&wait_start
, NULL
) < i
) {
728 /* wrap up if all targets are declared dead */
730 get_timevaldiff(&prog_start
, NULL
) >= max_completion_time
||
731 (mode
== MODE_HOSTCHECK
&& targets_down
))
736 /* reap responses until we hit a timeout */
737 n
= recvfrom_wto(sock
, buf
, sizeof(buf
),
738 (struct sockaddr
*)&resp_addr
, &t
, &now
);
741 printf("recvfrom_wto() timed out during a %u usecs wait\n",
744 continue; /* timeout for this one, so keep trying */
747 if(debug
) printf("recvfrom_wto() returned errors\n");
751 ip
= (struct ip
*)buf
;
752 if(debug
> 1) printf("received %u bytes from %s\n",
753 ntohs(ip
->ip_len
), inet_ntoa(resp_addr
.sin_addr
));
755 /* obsolete. alpha on tru64 provides the necessary defines, but isn't broken */
756 /* #if defined( __alpha__ ) && __STDC__ && !defined( __GLIBC__ ) */
757 /* alpha headers are decidedly broken. Using an ansi compiler,
758 * they provide ip_vhl instead of ip_hl and ip_v, so we mask
759 * off the bottom 4 bits */
760 /* hlen = (ip->ip_vhl & 0x0f) << 2; */
762 hlen
= ip
->ip_hl
<< 2;
765 if(n
< (hlen
+ ICMP_MINLEN
)) {
766 crash("received packet too short for ICMP (%d bytes, expected %d) from %s\n",
767 n
, hlen
+ icmp_pkt_size
, inet_ntoa(resp_addr
.sin_addr
));
769 /* else if(debug) { */
770 /* printf("ip header size: %u, packet size: %u (expected %u, %u)\n", */
771 /* hlen, ntohs(ip->ip_len) - hlen, */
772 /* sizeof(struct ip), icmp_pkt_size); */
775 /* check the response */
776 memcpy(&icp
, buf
+ hlen
, sizeof(icp
));
778 if(ntohs(icp
.icmp_id
) != pid
|| icp
.icmp_type
!= ICMP_ECHOREPLY
||
779 ntohs(icp
.icmp_seq
) >= targets
*packets
) {
780 if(debug
> 2) printf("not a proper ICMP_ECHOREPLY\n");
781 handle_random_icmp(buf
+ hlen
, &resp_addr
);
785 /* this is indeed a valid response */
786 memcpy(&data
, icp
.icmp_data
, sizeof(data
));
788 printf("ICMP echo-reply of len %lu, id %u, seq %u, cksum 0x%X\n",
789 (unsigned long)sizeof(data
), ntohs(icp
.icmp_id
),
790 ntohs(icp
.icmp_seq
), icp
.icmp_cksum
);
792 host
= table
[ntohs(icp
.icmp_seq
)/packets
];
793 tdiff
= get_timevaldiff(&data
.stime
, &now
);
795 host
->time_waited
+= tdiff
;
798 if (tdiff
> host
->rtmax
)
800 if (tdiff
< host
->rtmin
)
804 printf("%0.3f ms rtt from %s, outgoing ttl: %u, incoming ttl: %u, max: %0.3f, min: %0.3f\n",
805 (float)tdiff
/ 1000, inet_ntoa(resp_addr
.sin_addr
),
806 ttl
, ip
->ip_ttl
, (float)host
->rtmax
/ 1000, (float)host
->rtmin
/ 1000);
809 /* if we're in hostcheck mode, exit with limited printouts */
810 if(mode
== MODE_HOSTCHECK
) {
811 printf("OK - %s responds to ICMP. Packet %u, rta %0.3fms|"
812 "pkt=%u;;0;%u rta=%0.3f;%0.3f;%0.3f;;\n",
813 host
->name
, icmp_recv
, (float)tdiff
/ 1000,
814 icmp_recv
, packets
, (float)tdiff
/ 1000,
815 (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000);
823 /* the ping functions */
825 send_icmp_ping(int sock
, struct rta_host
*host
)
828 void *buf
; /* re-use so we prevent leaks */
833 struct icmp_ping_data data
;
837 struct sockaddr
*addr
;
841 crash("Attempt to send on bogus socket");
844 addr
= (struct sockaddr
*)&host
->saddr_in
;
847 if (!(packet
.buf
= malloc(icmp_pkt_size
))) {
848 crash("send_icmp_ping(): failed to malloc %d bytes for send buffer",
850 return -1; /* might be reached if we're in debug mode */
853 memset(packet
.buf
, 0, icmp_pkt_size
);
855 if((gettimeofday(&tv
, &tz
)) == -1) return -1;
857 data
.ping_id
= 10; /* host->icmp.icmp_sent; */
858 memcpy(&data
.stime
, &tv
, sizeof(tv
));
859 memcpy(&packet
.icp
->icmp_data
, &data
, sizeof(data
));
860 packet
.icp
->icmp_type
= ICMP_ECHO
;
861 packet
.icp
->icmp_code
= 0;
862 packet
.icp
->icmp_cksum
= 0;
863 packet
.icp
->icmp_id
= htons(pid
);
864 packet
.icp
->icmp_seq
= htons(host
->id
++);
865 packet
.icp
->icmp_cksum
= icmp_checksum(packet
.cksum_in
, icmp_pkt_size
);
868 printf("Sending ICMP echo-request of len %lu, id %u, seq %u, cksum 0x%X to host %s\n",
869 (unsigned long)sizeof(data
), ntohs(packet
.icp
->icmp_id
),
870 ntohs(packet
.icp
->icmp_seq
), packet
.icp
->icmp_cksum
,
873 memset(&iov
, 0, sizeof(iov
));
874 iov
.iov_base
= packet
.buf
;
875 iov
.iov_len
= icmp_pkt_size
;
877 memset(&hdr
, 0, sizeof(hdr
));
879 hdr
.msg_namelen
= sizeof(struct sockaddr
);
883 /* MSG_CONFIRM is a linux thing and only available on linux kernels >= 2.3.15, see send(2) */
885 len
= sendmsg(sock
, &hdr
, MSG_CONFIRM
);
887 len
= sendmsg(sock
, &hdr
, 0);
890 if(len
< 0 || (unsigned int)len
!= icmp_pkt_size
) {
891 if(debug
) printf("Failed to send ping to %s\n",
892 inet_ntoa(host
->saddr_in
.sin_addr
));
903 recvfrom_wto(int sock
, void *buf
, unsigned int len
, struct sockaddr
*saddr
,
904 u_int
*timo
, struct timeval
* tv
)
908 struct timeval to
, then
, now
;
914 struct cmsghdr
* chdr
;
918 if(debug
) printf("*timo is not\n");
922 to
.tv_sec
= *timo
/ 1000000;
923 to
.tv_usec
= (*timo
- (to
.tv_sec
* 1000000));
929 gettimeofday(&then
, &tz
);
930 n
= select(sock
+ 1, &rd
, &wr
, NULL
, &to
);
931 if(n
< 0) crash("select() in recvfrom_wto");
932 gettimeofday(&now
, &tz
);
933 *timo
= get_timevaldiff(&then
, &now
);
935 if(!n
) return 0; /* timeout */
937 slen
= sizeof(struct sockaddr
);
939 memset(&iov
, 0, sizeof(iov
));
943 memset(&hdr
, 0, sizeof(hdr
));
944 hdr
.msg_name
= saddr
;
945 hdr
.msg_namelen
= slen
;
948 hdr
.msg_control
= ans_data
;
949 hdr
.msg_controllen
= sizeof(ans_data
);
951 ret
= recvmsg(sock
, &hdr
, 0);
953 for(chdr
= CMSG_FIRSTHDR(&hdr
); chdr
; chdr
= CMSG_NXTHDR(&hdr
, chdr
)) {
954 if(chdr
->cmsg_level
== SOL_SOCKET
955 && chdr
->cmsg_type
== SO_TIMESTAMP
956 && chdr
->cmsg_len
>= CMSG_LEN(sizeof(struct timeval
))) {
957 memcpy(tv
, CMSG_DATA(chdr
), sizeof(*tv
));
962 #endif // SO_TIMESTAMP
963 gettimeofday(tv
, &tz
);
973 struct rta_host
*host
;
974 const char *status_string
[] =
975 {"OK", "WARNING", "CRITICAL", "UNKNOWN", "DEPENDENT"};
980 if(debug
> 1) printf("finish(%d) called\n", sig
);
982 if(icmp_sock
!= -1) close(icmp_sock
);
983 if(udp_sock
!= -1) close(udp_sock
);
984 if(tcp_sock
!= -1) close(tcp_sock
);
987 printf("icmp_sent: %u icmp_recv: %u icmp_lost: %u\n",
988 icmp_sent
, icmp_recv
, icmp_lost
);
989 printf("targets: %u targets_alive: %u\n", targets
, targets_alive
);
992 /* iterate thrice to calculate values, give output, and print perfparse */
995 if(!host
->icmp_recv
) {
996 /* rta 0 is ofcourse not entirely correct, but will still show up
997 * conspicuosly as missing entries in perfparse and cacti */
1000 status
= STATE_CRITICAL
;
1001 /* up the down counter if not already counted */
1002 if(!(host
->flags
& FLAG_LOST_CAUSE
) && targets_alive
) targets_down
++;
1005 pl
= ((host
->icmp_sent
- host
->icmp_recv
) * 100) / host
->icmp_sent
;
1006 rta
= (double)host
->time_waited
/ host
->icmp_recv
;
1010 if(pl
>= crit
.pl
|| rta
>= crit
.rta
) {
1011 status
= STATE_CRITICAL
;
1013 else if(!status
&& (pl
>= warn
.pl
|| rta
>= warn
.rta
)) {
1014 status
= STATE_WARNING
;
1023 /* this is inevitable */
1024 if(!targets_alive
) status
= STATE_CRITICAL
;
1025 if(min_hosts_alive
> -1) {
1026 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
1027 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
1029 printf("%s - ", status_string
[status
]);
1035 if(i
< targets
) printf(" :: ");
1039 if(!host
->icmp_recv
) {
1040 status
= STATE_CRITICAL
;
1041 if(host
->flags
& FLAG_LOST_CAUSE
) {
1042 printf("%s: %s @ %s. rta nan, lost %d%%",
1044 get_icmp_error_msg(host
->icmp_type
, host
->icmp_code
),
1045 inet_ntoa(host
->error_addr
),
1048 else { /* not marked as lost cause, so we have no flags for it */
1049 printf("%s: rta nan, lost 100%%", host
->name
);
1052 else { /* !icmp_recv */
1053 printf("%s: rta %0.3fms, lost %u%%",
1054 host
->name
, host
->rta
/ 1000, host
->pl
);
1060 /* iterate once more for pretty perfparse output */
1066 printf("%srta=%0.3fms;%0.3f;%0.3f;0; %spl=%u%%;%u;%u;; %srtmax=%0.3fms;;;; %srtmin=%0.3fms;;;; ",
1067 (targets
> 1) ? host
->name
: "",
1068 host
->rta
/ 1000, (float)warn
.rta
/ 1000, (float)crit
.rta
/ 1000,
1069 (targets
> 1) ? host
->name
: "", host
->pl
, warn
.pl
, crit
.pl
,
1070 (targets
> 1) ? host
->name
: "", (float)host
->rtmax
/ 1000,
1071 (targets
> 1) ? host
->name
: "", (host
->rtmin
< DBL_MAX
) ? (float)host
->rtmin
/ 1000 : (float)0);
1076 if(min_hosts_alive
> -1) {
1077 if(hosts_ok
>= min_hosts_alive
) status
= STATE_OK
;
1078 else if((hosts_ok
+ hosts_warn
) >= min_hosts_alive
) status
= STATE_WARNING
;
1081 /* finish with an empty line */
1083 if(debug
) printf("targets: %u, targets_alive: %u, hosts_ok: %u, hosts_warn: %u, min_hosts_alive: %i\n",
1084 targets
, targets_alive
, hosts_ok
, hosts_warn
, min_hosts_alive
);
1090 get_timevaldiff(struct timeval
*early
, struct timeval
*later
)
1096 gettimeofday(&now
, &tz
);
1099 if(!early
) early
= &prog_start
;
1101 /* if early > later we return 0 so as to indicate a timeout */
1102 if(early
->tv_sec
> later
->tv_sec
||
1103 (early
->tv_sec
== later
->tv_sec
&& early
->tv_usec
> later
->tv_usec
))
1108 ret
= (later
->tv_sec
- early
->tv_sec
) * 1000000;
1109 ret
+= later
->tv_usec
- early
->tv_usec
;
1115 add_target_ip(char *arg
, struct in_addr
*in
)
1117 struct rta_host
*host
;
1119 /* disregard obviously stupid addresses */
1120 if(in
->s_addr
== INADDR_NONE
|| in
->s_addr
== INADDR_ANY
)
1123 /* no point in adding two identical IP's, so don't. ;) */
1126 if(host
->saddr_in
.sin_addr
.s_addr
== in
->s_addr
) {
1127 if(debug
) printf("Identical IP already exists. Not adding %s\n", arg
);
1133 /* add the fresh ip */
1134 host
= malloc(sizeof(struct rta_host
));
1136 crash("add_target_ip(%s, %s): malloc(%d) failed",
1137 arg
, inet_ntoa(*in
), sizeof(struct rta_host
));
1139 memset(host
, 0, sizeof(struct rta_host
));
1141 /* set the values. use calling name for output */
1142 host
->name
= strdup(arg
);
1144 /* fill out the sockaddr_in struct */
1145 host
->saddr_in
.sin_family
= AF_INET
;
1146 host
->saddr_in
.sin_addr
.s_addr
= in
->s_addr
;
1148 host
->rtmin
= DBL_MAX
;
1150 if(!list
) list
= cursor
= host
;
1151 else cursor
->next
= host
;
1159 /* wrapper for add_target_ip */
1161 add_target(char *arg
)
1165 struct in_addr
*in
, ip
;
1167 /* don't resolve if we don't have to */
1168 if((ip
.s_addr
= inet_addr(arg
)) != INADDR_NONE
) {
1169 /* don't add all ip's if we were given a specific one */
1170 return add_target_ip(arg
, &ip
);
1171 /* he = gethostbyaddr((char *)in, sizeof(struct in_addr), AF_INET); */
1172 /* if(!he) return add_target_ip(arg, in); */
1176 he
= gethostbyname(arg
);
1179 crash("Failed to resolve %s", arg
);
1184 /* possibly add all the IP's as targets */
1185 for(i
= 0; he
->h_addr_list
[i
]; i
++) {
1186 in
= (struct in_addr
*)he
->h_addr_list
[i
];
1187 add_target_ip(arg
, in
);
1189 /* this is silly, but it works */
1190 if(mode
== MODE_HOSTCHECK
|| mode
== MODE_ALL
) {
1191 if(debug
> 2) printf("mode: %d\n", mode
);
1201 set_source_ip(char *arg
)
1203 struct sockaddr_in src
;
1205 memset(&src
, 0, sizeof(src
));
1206 src
.sin_family
= AF_INET
;
1207 if((src
.sin_addr
.s_addr
= inet_addr(arg
)) == INADDR_NONE
)
1208 src
.sin_addr
.s_addr
= get_ip_address(arg
);
1209 if(bind(icmp_sock
, (struct sockaddr
*)&src
, sizeof(src
)) == -1)
1210 crash("Cannot bind to IP address %s", arg
);
1213 /* TODO: Move this to netutils.c and also change check_dhcp to use that. */
1215 get_ip_address(const char *ifname
)
1217 #if defined(SIOCGIFADDR)
1219 struct sockaddr_in ip
;
1221 strncpy(ifr
.ifr_name
, ifname
, sizeof(ifr
.ifr_name
) - 1);
1222 ifr
.ifr_name
[sizeof(ifr
.ifr_name
) - 1] = '\0';
1223 if(ioctl(icmp_sock
, SIOCGIFADDR
, &ifr
) == -1)
1224 crash("Cannot determine IP address of interface %s", ifname
);
1225 memcpy(&ip
, &ifr
.ifr_addr
, sizeof(ip
));
1226 return ip
.sin_addr
.s_addr
;
1229 crash("Cannot get interface IP address on this platform.");
1237 * return value is in microseconds
1240 get_timevar(const char *str
)
1244 u_int i
, d
; /* integer and decimal, respectively */
1245 u_int factor
= 1000; /* default to milliseconds */
1251 /* unit might be given as ms|m (millisec),
1252 * us|u (microsec) or just plain s, for seconds */
1255 if(len
>= 2 && !isdigit((int)str
[len
- 2])) p
= str
[len
- 2];
1256 if(p
&& u
== 's') u
= p
;
1258 if(debug
> 2) printf("evaluating %s, u: %c, p: %c\n", str
, u
, p
);
1260 if(u
== 'u') factor
= 1; /* microseconds */
1261 else if(u
== 'm') factor
= 1000; /* milliseconds */
1262 else if(u
== 's') factor
= 1000000; /* seconds */
1263 if(debug
> 2) printf("factor is %u\n", factor
);
1265 i
= strtoul(str
, &ptr
, 0);
1266 if(!ptr
|| *ptr
!= '.' || strlen(ptr
) < 2 || factor
== 1)
1269 /* time specified in usecs can't have decimal points, so ignore them */
1270 if(factor
== 1) return i
;
1272 d
= strtoul(ptr
+ 1, NULL
, 0);
1274 /* d is decimal, so get rid of excess digits */
1275 while(d
>= factor
) d
/= 10;
1277 /* the last parenthesis avoids floating point exceptions. */
1278 return ((i
* factor
) + (d
* (factor
/ 10)));
1281 /* not too good at checking errors, but it'll do (main() should barfe on -1) */
1283 get_threshold(char *str
, threshold
*th
)
1285 char *p
= NULL
, i
= 0;
1287 if(!str
|| !strlen(str
) || !th
) return -1;
1289 /* pointer magic slims code by 10 lines. i is bof-stop on stupid libc's */
1290 p
= &str
[strlen(str
) - 1];
1291 while(p
!= &str
[1]) {
1292 if(*p
== '%') *p
= '\0';
1293 else if(*p
== ',' && i
) {
1294 *p
= '\0'; /* reset it so get_timevar(str) works nicely later */
1295 th
->pl
= (unsigned char)strtoul(p
+1, NULL
, 0);
1301 th
->rta
= get_timevar(str
);
1303 if(!th
->rta
) return -1;
1305 if(th
->rta
> MAXTTL
* 1000000) th
->rta
= MAXTTL
* 1000000;
1306 if(th
->pl
> 100) th
->pl
= 100;
1312 icmp_checksum(unsigned short *p
, int n
)
1314 register unsigned short cksum
;
1315 register long sum
= 0;
1322 /* mop up the occasional odd byte */
1323 if(n
== 1) sum
+= (unsigned char)*p
;
1325 sum
= (sum
>> 16) + (sum
& 0xffff); /* add hi 16 to low 16 */
1326 sum
+= (sum
>> 16); /* add carry */
1327 cksum
= ~sum
; /* ones-complement, trunc to 16 bits */
1336 /*print_revision (progname);*/ /* FIXME: Why? */
1338 printf ("Copyright (c) 2005 Andreas Ericsson <ae@op5.se>\n");
1339 printf (COPYRIGHT
, copyright
, email
);
1345 printf (UT_HELP_VRSN
);
1346 printf (UT_EXTRA_OPTS
);
1348 printf (" %s\n", "-H");
1349 printf (" %s\n", _("specify a target"));
1350 printf (" %s\n", "-w");
1351 printf (" %s", _("warning threshold (currently "));
1352 printf ("%0.3fms,%u%%)\n", (float)warn
.rta
/ 1000, warn
.pl
);
1353 printf (" %s\n", "-c");
1354 printf (" %s", _("critical threshold (currently "));
1355 printf ("%0.3fms,%u%%)\n", (float)crit
.rta
/ 1000, crit
.pl
);
1356 printf (" %s\n", "-s");
1357 printf (" %s\n", _("specify a source IP address or device name"));
1358 printf (" %s\n", "-n");
1359 printf (" %s", _("number of packets to send (currently "));
1360 printf ("%u)\n",packets
);
1361 printf (" %s\n", "-i");
1362 printf (" %s", _("max packet interval (currently "));
1363 printf ("%0.3fms)\n",(float)pkt_interval
/ 1000);
1364 printf (" %s\n", "-I");
1365 printf (" %s", _("max target interval (currently "));
1366 printf ("%0.3fms)\n", (float)target_interval
/ 1000);
1367 printf (" %s\n", "-m");
1368 printf (" %s",_("number of alive hosts required for success"));
1370 printf (" %s\n", "-l");
1371 printf (" %s", _("TTL on outgoing packets (currently "));
1372 printf ("%u)\n", ttl
);
1373 printf (" %s\n", "-t");
1374 printf (" %s",_("timeout value (seconds, currently "));
1375 printf ("%u)\n", timeout
);
1376 printf (" %s\n", "-b");
1377 printf (" %s\n", _("Number of icmp data bytes to send"));
1378 printf (" %s %u + %d)\n", _("Packet size will be data bytes + icmp header (currently"),icmp_data_size
, ICMP_MINLEN
);
1379 printf (" %s\n", "-v");
1380 printf (" %s\n", _("verbose"));
1383 printf ("%s\n", _("Notes:"));
1384 printf (" %s\n", _("The -H switch is optional. Naming a host (or several) to check is not."));
1386 printf (" %s\n", _("Threshold format for -w and -c is 200.25,60% for 200.25 msec RTA and 60%"));
1387 printf (" %s\n", _("packet loss. The default values should work well for most users."));
1388 printf (" %s\n", _("You can specify different RTA factors using the standardized abbreviations"));
1389 printf (" %s\n", _("us (microseconds), ms (milliseconds, default) or just plain s for seconds."));
1390 /* -d not yet implemented */
1391 /* printf ("%s\n", _("Threshold format for -d is warn,crit. 12,14 means WARNING if >= 12 hops"));
1392 printf ("%s\n", _("are spent and CRITICAL if >= 14 hops are spent."));
1393 printf ("%s\n\n", _("NOTE: Some systems decrease TTL when forming ICMP_ECHOREPLY, others do not."));*/
1395 printf (" %s\n", _("The -v switch can be specified several times for increased verbosity."));
1396 /* printf ("%s\n", _("Long options are currently unsupported."));
1397 printf ("%s\n", _("Options marked with * require an argument"));
1400 printf (UT_SUPPORT
);
1408 printf ("%s\n", _("Usage:"));
1409 printf(" %s [options] [-H] host1 host2 hostN\n", progname
);