* 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"
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 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
)
while((param
= getopt(ac
, av
, "rvf:")) != -1)
fprintf(stderr
,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n");
fprintf(stderr
, "ERROR: No config file specified.\n");
(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
)
if (isVerbose
) printf("INFO: Host %s is down. Executing DOWN command.\n", 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
;
if (isVerbose
) printf("INFO: Sending ICMP packet to %s.\n", 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
);
if (i
<0) fprintf(stderr
, "WARN: Failed sending ICMP packet to %s.\n", 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.
if(p
->socket
!=-1 && FD_ISSET(p
->socket
, &rfds
))
/* TODO: How to handle this error? */
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) {
if (errno
!= EINTR
) fprintf(stderr
, "WARN: Error reading ICMP data from %s.\n", p
->name
);
/* check IP header actual len */
iphdrlen
= ip
->ip_hl
<<2 ;
icmp
= (struct icmp
*) (buf
+iphdrlen
) ;
if (cc
< iphdrlen
+ ICMP_MINLEN
) {
fprintf(stderr
, "WARN: Received short packet from %s.\n", 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);
if(isVerbose
) printf("INFO: Got ICMP reply from %s in %d ms.\n", p
->name
, delay
);
if (isVerbose
) printf("INFO: Host %s is up. Executing UP command.\n", p
->name
);
/* TODO: Do anything here? */
static void read_hosts(const char *cfg_file_name
)
if ((cfg
= readcfg(cfg_file_name
)) == NULL
) {
fprintf(stderr
, "ERROR: Failed to read config.\n");
hosts
= malloc(sizeof(monitor_host_t
*) * cfg
->nelements
);
for (i
=0; i
<cfg
->nelements
; i
++) {
if (cfg
->dict
[i
]->nvalues
< 4) {
fprintf(stderr
, "ERROR: Not enough fields in record %d of cfg file. Got %d.\n", n
, cfg
->dict
[i
]->nvalues
+1);
} else if (cfg
->dict
[i
]->nvalues
>5) {
fprintf(stderr
, "ERROR: Too many fields in record %d of cfg file. Got %d.\n", 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)
fprintf(stderr
, "ERROR: Illegal value %s in record %n for startup condition.\n", cfg
->dict
[i
]->value
[4], n
);
hosts
[n
]->sentpackets
= 0;
hosts
[n
]->recvdpackets
= 0;
if (n
> 0) hosts
[n
-1]->next
=hosts
[n
];
gettimeofday(&(hosts
[n
]->last_ping_received
), (struct timezone
*)NULL
);
fprintf(stderr
, "ERROR: No hosts defined in cfg file, exiting.\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
) {
fprintf(stderr
, "ERROR: Unknown protocol: icmp.\n");
bzero(&p
->dest
,sizeof(p
->dest
));
p
->dest
.sin_family
=AF_INET
;
if((p
->dest
.sin_addr
.s_addr
=gethostaddr(p
->name
))<=0)
fprintf(stderr
, "WARN: Can't resolve host. Skipping client %s.\n", p
->name
);
if((p
->socket
=socket(AF_INET
,SOCK_RAW
,proto
->p_proto
))<0)
fprintf(stderr
, "WARN: Can't create socket. Skipping client %s.\n", p
->name
);
send_delay
= p
->ping_interval
;
send_delay
= gcd(send_delay
, p
->ping_interval
);
fprintf(stderr
, "ERROR: No hosts left to process.\n");
* 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
;
static int gcd(int x
, int y
)
if (remainder
== 0) return y
;
return gcd(y
, remainder
);