* 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/ip_icmp.h>
#define MAXPACKETSIZE (65536 - 60 - 8) /* TODO: What are the magic numbers? */
#define DEFAULTDATALEN (64 - 8) /* TODO: What are the magic numbers? */
/* From the config file */
struct timeval last_ping_received
;
struct timeval last_ping_sent
;
unsigned int sentpackets
;
unsigned int recvdpackets
;
struct monitor_host
* next
;
static struct monitor_host
** hosts
= NULL
;
static int isVerbose
= 0;
static int keepBanging
= 0;
static unsigned short ident
;
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.
tvsub(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
);
tvsub(&now
, &p
->last_ping_received
);
if (now
.tv_sec
> (p
->max_delay
+ p
->ping_interval
)) {
if ((p
->hostup
) || keepBanging
) {
if (isVerbose
) printf("INFO: Host %s stopped responding. Executing DOWN command.\n", p
->name
);
gettimeofday(&now
, (struct timezone
*) NULL
);
tvsub(&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
;
if (isVerbose
) 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
== 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 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? */
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(struct monitor_host
*) * 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(struct monitor_host
));
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) {
hosts
[n
]->hostup
= false;
} else if (strcmp(cfg
->dict
[i
]->value
[4], "auto") == 0) {
/* TODO: Send a ping and set initial state accordingly. */
fprintf(stderr
, "ERROR: Illegal value %s in record %d 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");
gethostaddr(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
= 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
);
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");
main(int argc
, char ** argv
)
while ((param
= getopt(argc
, argv
, "rvf:")) != -1) {
fprintf(stderr
,"Usage: icmpmonitor [-v] [-r] [-f cfgfile]\n");
fprintf(stderr
, "ERROR: No config file specified.\n");
ident
= getpid() & 0xFFFF;