* Monitor hosts using ICMP "echo" and notify when down.
* © 2019 Aaron Taylor <ataylor at subgeniuskitty dot com>
* © 1999 Vadim Zaliva <lord@crocodile.org>
* © 1989 The Regents of the University of California & Mike Muuss
* See LICENSE file for copyright and license details.
#include <netinet/in_systm.h>
#include <netinet/ip_icmp.h>
#define MAXPACKET (65536 - 60 - 8) /* max packet size */
#define DEFDATALEN (64 - 8) /* default data length */
#define VERSION "ICMPmonitor v1.2 by lord@crocodile.org"
#define MAX_LOG_MSG_SIZE 4096
typedef struct monitor_host
/* following are coming from cfg */
/* following values are calculated */
struct timeval last_ping_received
;
struct timeval last_ping_sent
;
unsigned int sentpackets
;
unsigned int recvdpackets
;
struct monitor_host
*next
;
static void log(int type
, char *format
, ...);
static int gethostaddr(const char *name
);
static void read_hosts(const char *cfg_file_name
);
static void init_hosts(void);
static void get_response(void);
static int in_cksum(u_short
*addr
, int len
);
static void read_icmp_data(monitor_host_t
*p
);
static void tvsub(struct timeval
*out
, struct timeval
*in
);
static void done(int code
);
static int gcd(int x
, int y
);
static monitor_host_t
**hosts
= NULL
;
static int isVerbose
= 0;
static int keepBanging
= 0;
static unsigned short ident
;
static int send_delay
= 1;
int main(int ac
, char **av
)
log(LOG_INFO
, VERSION
" is starting.");
while((param
= getopt(ac
, av
, "rvf:")) != -1)
fprintf(stderr
,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n");
log(LOG_WARNING
,"No cfg file specified. Assuming 'icmpmonitor.cfg'");
cfgfile
="icmpmonitor.cfg";
(void)signal(SIGALRM
, pinger
);
* Checksum routine for Internet Protocol family headers (C Version)
in_cksum(u_short
*addr
, int len
)
register int nleft
= len
;
register u_short
*w
= addr
;
* Our algorithm is simple, using a 32 bit accumulator (sum), we add
* sequential 16 bit words to it, and at the end, fold back all the
* carry bits from the top 16 bits into the lower 16 bits.
/* mop up an odd byte, if necessary */
*(u_char
*)(&answer
) = *(u_char
*)w
;
/* add back carry outs from top 16 bits to low 16 bits */
sum
= (sum
>> 16) + (sum
& 0xffff); /* add hi 16 to low 16 */
sum
+= (sum
>> 16); /* add carry */
answer
= ~sum
; /* truncate to 16 bits */
* Compose and transmit an ICMP ECHO REQUEST packet. The IP packet
* will be added on by the kernel. The ID field is our UNIX process ID,
* and the sequence number is an ascending integer. The first 8 bytes
* of the data portion are used to hold a UNIX "timeval" struct in VAX
* byte-order, to compute the round-trip time.
static void pinger(int ignore
)
register struct icmphdr
*icp
;
u_char outpack
[MAXPACKET
];
(void)gettimeofday(&now
,(struct timezone
*)NULL
);
tvsub(&now
, &p
->last_ping_received
);
if(now
.tv_sec
> (p
->max_delay
+p
->ping_interval
))
if((!p
->down
) || keepBanging
)
log(LOG_INFO
,"Host %s in down. Executing DOWN command",p
->name
);
(void)gettimeofday(&now
,(struct timezone
*)NULL
);
tvsub(&now
, &p
->last_ping_sent
);
if(now
.tv_sec
> p
->ping_interval
)
icp
= (struct icmphdr
*)outpack
;
icp
->icmp_type
= ICMP_ECHO
;
icp
->icmp_seq
= p
->socket
;
log(LOG_INFO
,"Sending ICMP packet to %s.",p
->name
);
(void)gettimeofday((struct timeval
*)&outpack
[8],
(struct timezone
*)NULL
);
cc
= DEFDATALEN
+ 8; /* skips ICMP portion */
/* compute ICMP checksum here */
icp
->icmp_cksum
= in_cksum((u_short
*)icp
, cc
);
i
= sendto(p
->socket
, (char *)outpack
, cc
, 0, (const struct sockaddr
*)(&p
->dest
),
sizeof(struct sockaddr
));
(void)gettimeofday(&p
->last_ping_sent
,
(struct timezone
*)NULL
);
log(LOG_WARNING
,"Sending ICMP packet to %s failed.",p
->name
);
(void)signal(SIGALRM
, pinger
); /* restore handler */
static void get_response(void)
FD_SET(p
->socket
, &rfds
);
retval
= select(maxd
+1, &rfds
, NULL
, NULL
, NULL
);
/* we get her in case we are interrupted by signal.
/* log(LOG_DEBUG,"ICMP data is available now."); */
if(p
->socket
!=-1 && FD_ISSET(p
->socket
, &rfds
))
log(LOG_DEBUG
,"select returns 0."); /* TODO */
static void read_icmp_data(monitor_host_t
*p
)
struct sockaddr_in from
;
unsigned char buf
[MAXPACKET
]; /* read buffer */
(void)gettimeofday(&tv
, (struct timezone
*)NULL
);
if((cc
= recvfrom(p
->socket
, buf
, sizeof(buf
), 0,
(struct sockaddr
*)&from
, &fromlen
)) < 0)
log(LOG_WARNING
,"Error reading ICMP data from %s.",p
->name
);
/* log(LOG_DEBUG,"Got %d bytes of ICMP data from %s.",cc, p->name); */
/* check IP header actual len */
iphdrlen
= ip
->ip_hl
<<2 ;
icmp
= (struct icmp
*) (buf
+iphdrlen
) ;
if(cc
< iphdrlen
+ICMP_MINLEN
)
log(LOG_WARNING
,"Received short packet from %s.",p
->name
);
if(icmp
->icmp_type
== ICMP_ECHOREPLY
&&
icmp
->icmp_id
== ident
&&
icmp
->icmp_seq
== p
->socket
)
memcpy(&p
->last_ping_received
, &tv
, sizeof(tv
));
tvsub(&tv
, (struct timeval
*) &icmp
->icmp_data
[0]);
delay
=tv
.tv_sec
*1000+(tv
.tv_usec
/1000);
log(LOG_INFO
,"Got ICMP reply from %s in %d ms.",p
->name
,delay
);
log(LOG_INFO
,"Host %s in now up. Executing UP command",p
->name
);
log(LOG_DEBUG,"ICMP packet of type %d from %s. Ident=%d",icmp->icmp_type,
static void read_hosts(const char *cfg_file_name
)
if((cfg
=readcfg(cfg_file_name
))==NULL
)
log(LOG_ERR
,"Error reading cfg. Exiting.");
hosts
=malloc(sizeof(monitor_host_t
*)*cfg
->nelements
);
for(i
=0;i
<cfg
->nelements
;i
++)
if(cfg
->dict
[i
]->nvalues
<4)
log(LOG_ERR
,"Not enough fields in record %d of cfg file. Got %d.",n
, cfg
->dict
[i
]->nvalues
+1);
} else if(cfg
->dict
[i
]->nvalues
>5)
log(LOG_ERR
,"Too many fields in record %d of cfg file. Got %d.",n
, cfg
->dict
[i
]->nvalues
+1);
hosts
[n
]=malloc(sizeof(monitor_host_t
));
hosts
[n
]->name
= strdup(cfg
->dict
[i
]->name
);
hosts
[n
]->ping_interval
= atoi (cfg
->dict
[i
]->value
[0]);
hosts
[n
]->max_delay
= atoi (cfg
->dict
[i
]->value
[1]);
hosts
[n
]->upcmd
= strdup(cfg
->dict
[i
]->value
[2]);
hosts
[n
]->downcmd
= strdup(cfg
->dict
[i
]->value
[3]);
if(cfg
->dict
[i
]->nvalues
==4)
} else if(strcmp(cfg
->dict
[i
]->value
[4], "up")==0)
} else if(strcmp(cfg
->dict
[i
]->value
[4], "down")==0)
} else if(strcmp(cfg
->dict
[i
]->value
[4], "auto")==0)
} else if(strcmp(cfg
->dict
[i
]->value
[4], "none")==0)
log(LOG_ERR
,"Illegal value %s in record %n for startup condition.", cfg
->dict
[i
]->value
[4], n
);
hosts
[n
]->sentpackets
= 0;
hosts
[n
]->recvdpackets
= 0;
hosts
[n
-1]->next
=hosts
[n
];
(void)gettimeofday(&(hosts
[n
]->last_ping_received
), (struct timezone
*)NULL
);
log(LOG_ERR
,"No hosts defined in cfg file, exiting.");
log(LOG_DEBUG
,"%d host(s) found in cfg file,", n
);
static int gethostaddr(const char *name
)
if((res
=inet_addr(name
))<0)
memcpy( &res
, he
->h_addr
, he
->h_length
);
static void init_hosts(void)
monitor_host_t
*p
=hosts
[0];
if((proto
=getprotobyname("icmp"))==NULL
)
log(LOG_ERR
,"Unknown protocol: icmp. Exiting.");
log(LOG_DEBUG
,"resolving host %s", p
->name
);
bzero(&p
->dest
,sizeof(p
->dest
));
p
->dest
.sin_family
=AF_INET
;
if((p
->dest
.sin_addr
.s_addr
=gethostaddr(p
->name
))<=0)
log(LOG_ERR
,"Can't resolve host. Skipping client %s.",p
->name
);
if((p
->socket
=socket(AF_INET
,SOCK_RAW
,proto
->p_proto
))<0)
log(LOG_ERR
,"Can't create socket. Skipping client %s.",p
->name
);
send_delay
= p
->ping_interval
;
send_delay
= gcd(send_delay
, p
->ping_interval
);
log(LOG_ERR
,"No hosts left to process, exiting.");
* Subtract 2 timeval structs: out = out - in. Out is assumed to
tvsub(register struct timeval
*out
, register struct timeval
*in
)
if ((out
->tv_usec
-= in
->tv_usec
) < 0) {
out
->tv_sec
-= in
->tv_sec
;
* This function should be used as central logging facility.
* 'type' argument should be one of following:
* LOG_EMERG system is unusable
* LOG_ALERT action must be taken immediately
* LOG_CRIT critical conditions
* LOG_ERR error conditions
* LOG_WARNING warning conditions
* LOG_NOTICE normal but significant condition
* LOG_DEBUG debug-level messages
static void log(int type
, char *format
, ...)
fprintf(stderr
, "icmpmonitor[%d]:", (int)getpid());
vfprintf(stderr
, format
, ap
);
static int gcd(int x
, int y
)