* Monitor hosts using ICMP "echo" and notify when down.
* TODO: Write a better description.
* © 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/ip_icmp.h>
#include "iniparser/iniparser.h"
#define MAXPACKETSIZE (65536 - 60 - 8) /* TODO: What are the magic numbers? */
#define DEFAULTDATALEN (64 - 8) /* TODO: What are the magic numbers? */
/* Must be larger than the length of the longest configuration key (currently 'start_condition'). */
/* One struct per host as listed in the config file. */
/* From the config file */
struct timeval last_ping_received
;
struct timeval last_ping_sent
;
unsigned int sent_packets
;
unsigned int recvd_packets
;
struct monitor_host
* next
;
/* Since the program is based around signals, a linked list of hosts is maintained here. */
static struct monitor_host
** hosts
= NULL
;
/* Set by command line flags. */
static bool verbose
= false;
static bool retry_down_cmd
= false;
/* TODO: Get rid of this global. */
static int send_delay
= 1;
* Checksum routine for Internet Protocol family headers
in_cksum(unsigned short * addr
, int len
)
unsigned short * w
= addr
;
unsigned short answer
= 0;
* 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 */
* Subtracts two timeval structs.
* Modifies out = out - in.
tv_sub(register struct timeval
* out
, register struct timeval
* in
)
if ((out
->tv_usec
-= in
->tv_usec
) < 0) {
out
->tv_sec
-= in
->tv_sec
;
* 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.
u_char outpack
[MAXPACKETSIZE
];
gettimeofday(&now
, (struct timezone
*) NULL
);
tv_sub(&now
, &p
->last_ping_received
);
if (now
.tv_sec
> (p
->max_delay
+ p
->ping_interval
)) {
if ((p
->host_up
) || retry_down_cmd
) {
if (verbose
) printf("INFO: Host %s stopped responding. Executing DOWN command.\n", p
->name
);
gettimeofday(&now
, (struct timezone
*) NULL
);
tv_sub(&now
, &p
->last_ping_sent
);
if (now
.tv_sec
> p
->ping_interval
) { /* Time to send ping */
icp
= (struct icmp
*) outpack
;
icp
->icmp_type
= ICMP_ECHO
;
icp
->icmp_seq
= p
->socket
;
icp
->icmp_id
= getpid() & 0xFFFF;
if (verbose
) printf("INFO: Sending ICMP packet to %s.\n", p
->name
);
gettimeofday((struct timeval
*) &outpack
[8], (struct timezone
*) NULL
);
cc
= DEFAULTDATALEN
+ 8; /* skips ICMP portion */
/* compute ICMP checksum */
icp
->icmp_cksum
= in_cksum((unsigned short *) icp
, cc
);
i
= sendto(p
->socket
, (char *) outpack
, cc
, 0, (const struct sockaddr
*) (&p
->dest
), sizeof(struct sockaddr
));
gettimeofday(&p
->last_ping_sent
, (struct timezone
*) NULL
);
if (i
<0) fprintf(stderr
, "WARN: Failed sending ICMP packet to %s.\n", p
->name
);
signal(SIGALRM
, pinger
); /* restore handler */
read_icmp_data(struct monitor_host
* p
)
unsigned char buf
[MAXPACKETSIZE
];
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
== (getpid() & 0xFFFF) && icmp
->icmp_seq
== p
->socket
) {
memcpy(&p
->last_ping_received
, &tv
, sizeof(tv
));
tv_sub(&tv
, (struct timeval
*) &icmp
->icmp_data
[0]);
delay
= tv
.tv_sec
* 1000 + (tv
.tv_usec
/ 1000);
if (verbose
) printf("INFO: Got ICMP reply from %s in %d ms.\n", p
->name
, delay
);
if (verbose
) printf("INFO: Host %s started responding. Executing UP command.\n", p
->name
);
/* TODO: Do anything here? */
if (p
->socket
> maxd
) maxd
=p
->socket
;
FD_SET(p
->socket
, &rfds
);
retval
= select(maxd
+1, &rfds
, NULL
, NULL
, NULL
);
/* Intentionally empty. We arrive here when interrupted by a signal. No action should be taken. */
if (p
->socket
!=-1 && FD_ISSET(p
->socket
, &rfds
)) read_icmp_data(p
);
/* TODO: How to handle this error? */
* Parses `config_file` and creates relevant entries under the global variable `hosts`.
parse_config(const char * conf_file
)
dictionary
* conf
= iniparser_load(conf_file
);
fprintf(stderr
, "ERROR: Unable to parse configuration file %s.\n", conf_file
);
int host_count
= iniparser_getnsec(conf
);
fprintf(stderr
, "ERROR: Unable to determine number of hosts in configuration file.\n");
hosts
= malloc(sizeof(struct monitor_host
*) * host_count
);
for (int i
=0; i
< host_count
; i
++) {
/* Allocate a buffer large enough to hold the full 'section:key' string. */
int section_len
= strlen(iniparser_getsecname(conf
, i
));
char * key_buf
= malloc(section_len
+ 1 + MAXCONFKEYLEN
+ 1);
strcpy(key_buf
, iniparser_getsecname(conf
, i
));
key_buf
[section_len
++] = ':';
hosts
[i
] = malloc(sizeof(struct monitor_host
));
for (int k
=0; k
<6; k
++) {
key_buf
[section_len
] = '\0'; /* Reuse the section name and colon on each pass through this loop. */
strncat(key_buf
, "host", MAXCONFKEYLEN
);
hosts
[i
]->name
= strdup(iniparser_getstring(conf
, key_buf
, NULL
));
strncat(key_buf
, "interval", MAXCONFKEYLEN
);
hosts
[i
]->ping_interval
= iniparser_getint(conf
, key_buf
, -1);
strncat(key_buf
, "max_delay", MAXCONFKEYLEN
);
hosts
[i
]->max_delay
= iniparser_getint(conf
, key_buf
, -1);
strncat(key_buf
, "up_cmd", MAXCONFKEYLEN
);
hosts
[i
]->up_cmd
= strdup(iniparser_getstring(conf
, key_buf
, NULL
));
strncat(key_buf
, "down_cmd", MAXCONFKEYLEN
);
hosts
[i
]->down_cmd
= strdup(iniparser_getstring(conf
, key_buf
, NULL
));
/* TODO: Parse for up/down/auto in start_condition. */
/* TODO: Do a host up/down check if necessary. */
hosts
[i
]->host_up
= true;
/* TODO: Do I want to make any checks for up_cmd, down_cmd, and start_condition? */
if (hosts
[i
]->name
== NULL
|| hosts
[i
]->ping_interval
== -1 || hosts
[i
]->max_delay
== -1) {
fprintf(stderr
, "ERROR: Problems parsing section %s.\n", iniparser_getsecname(conf
, i
));
hosts
[i
]->sent_packets
= 0;
hosts
[i
]->recvd_packets
= 0;
if (i
>0) hosts
[i
-1]->next
= hosts
[i
];
gettimeofday(&(hosts
[i
]->last_ping_received
), (struct timezone
*) NULL
);
iniparser_freedict(conf
);
get_host_addr(const char * name
)
if ((res
= inet_addr(name
)) < 0) {
he
= gethostbyname(name
);
memcpy(&res
, he
->h_addr
, he
->h_length
);
if (remainder
== 0) return y
;
return gcd(y
, remainder
);
struct monitor_host
* 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
= get_host_addr(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
);
if (ok
== 0) send_delay
= p
->ping_interval
;
else send_delay
= gcd(send_delay
, p
->ping_interval
);
fprintf(stderr
, "ERROR: No hosts left to process.\n");
fprintf(stderr
,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n");
parse_params(int argc
, char ** argv
)
while ((param
= getopt(argc
, argv
, "rvf:")) != -1) {
fprintf(stderr
, "ERROR: Unable to parse a config file.\n");
main(int argc
, char ** argv
)
parse_params(argc
, argv
);