/* This file is part of Ping903
Copyright (C) 2020 Sergey Poznyakoff
Ping903 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
Ping903 is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Ping903. If not, see <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <pthread.h>
#include <limits.h>
#include <poll.h>
#include "ping903.h"
#include "json.h"
#include "defs.h"
/* Time in seconds between two subsequent probes. */
unsigned long probe_interval = 60;
/* Time between two subsequent echo requests within the same probe. */
unsigned long ping_interval = 1;
/* Number of echo requests per probe */
unsigned long ping_count = 10;
/* Number of unanswered echo requests after which the host is declared dead. */
unsigned long ping_tolerance = 3;
/* Initial value for the tmin member of struct hostping */
#define HOSTPING_TMIN_INIT 999999999.0
static double
nabs(double a)
{
return (a < 0) ? -a : a;
}
static double
nsqrt(double a, double prec)
{
double x0, x1;
if (a < 0)
return 0;
if (a < prec)
return 0;
x1 = a / 2;
do {
x0 = x1;
x1 = (x0 + a / x0) / 2;
} while (nabs(x1 - x0) > prec);
return x1;
}
static inline int
timeval_is_null(struct timeval const *tv)
{
return tv->tv_sec == 0 && tv->tv_usec == 0;
}
static inline double
timeval_to_double(struct timeval const *tv)
{
return (double)tv->tv_sec + (double)tv->tv_usec / 1000000;
}
enum { NANOSEC_IN_SEC = 1000000000 };
static inline void
timespec_add(struct timespec const *a, struct timespec const *b,
struct timespec *res)
{
res->tv_sec = a->tv_sec + b->tv_sec;
res->tv_nsec = a->tv_nsec + b->tv_nsec;
if (res->tv_nsec >= NANOSEC_IN_SEC) {
++res->tv_sec;
res->tv_nsec -= NANOSEC_IN_SEC;
}
}
static inline void
timespec_incr(struct timespec *a, struct timespec const *b)
{
struct timespec t = *a;
timespec_add(&t, b, a);
}
static void
hostping_free(HOSTPING *hp)
{
free(hp->name);
free(hp->addr);
pthread_mutex_destroy(&hp->mutex);
free(hp);
}
static HOSTPING *
hostping_create(char const *name, struct sockaddr *addr, socklen_t addrlen)
{
HOSTPING *hp;
size_t hostsize = sizeof(*hp) +
(ping_count - 1) * sizeof(hp->nreply[0]);
hp = malloc(hostsize);
if (hp) {
memset(hp, 0, hostsize);
hp->tmin = HOSTPING_TMIN_INIT;
hp->name = strdup(name);
if (!hp)
goto err;
hp->addr = malloc(addrlen);
if (!hp)
goto err;
memcpy(hp->addr, addr, addrlen);
hp->addrlen = addrlen;
pthread_mutex_init(&hp->mutex, NULL);
}
return hp;
err:
hostping_free(hp);
return NULL;
}
static inline void
hostping_lock(HOSTPING *host)
{
pthread_mutex_lock(&host->mutex);
}
static inline void
hostping_unlock(HOSTPING *host)
{
pthread_mutex_unlock(&host->mutex);
}
typedef struct hostlist {
HOSTPING *head, *tail;
size_t count;
} HOSTLIST;
static HOSTLIST *
hostlist_create(void)
{
HOSTLIST *hl = malloc(sizeof(*hl));
if (hl) {
hl->head = hl->tail = NULL;
hl->count = 0;
}
return hl;
}
static void
hostlist_free(HOSTLIST *list)
{
HOSTPING *hp = list->head;
while (hp) {
HOSTPING *next = hp->next;
hostping_free(hp);
hp = next;
}
free(list);
}
static void
hostlist_add(HOSTLIST *list, HOSTPING *hp)
{
hp->prev = list->tail;
if (list->tail)
list->tail->next = hp;
else
list->head = hp;
list->tail = hp;
list->count++;
}
static void
hostlist_remove(HOSTLIST *list, HOSTPING *hp)
{
if (hp->prev)
hp->prev->next = hp->next;
else
list->head = hp->next;
if (hp->next)
hp->next->prev = hp->prev;
else
list->tail = hp->prev;
list->count--;
}
static void
hostlist_concat(HOSTLIST *a, HOSTLIST *b)
{
if (b->head)
b->head->prev = a->tail;
if (a->tail)
a->tail->next = b->head;
else
a->head = b->head;
a->tail = b->head;
a->count += b->count;
b->head = b->tail = NULL;
b->count = 0;
}
static HOSTLIST *hostlist;
static HOSTPING *conf_hostping_tail;
static pthread_rwlock_t hostlist_rwlock = PTHREAD_RWLOCK_INITIALIZER;
typedef enum update_type {
UPDATE_APPEND,
UPDATE_REPLACE,
UPDATE_DELETE
} UPDATE_TYPE;
static pthread_mutex_t update_mutex = PTHREAD_MUTEX_INITIALIZER;
static int check_host(char const *name);
static int update_add(UPDATE_TYPE t, void *data);
void
pinger_setup(void)
{
hostlist = hostlist_create();
if (!hostlist)
emalloc_die();
}
int
pinger_host_add(char const *name, struct sockaddr *addr, socklen_t addrlen)
{
HOSTPING *hp = hostping_create(name, addr, addrlen);
if (!hp)
return -1;
hostlist_add(hostlist, hp);
return 0;
}
#define FOR_EACH_HOSTPING(hp) for (hp = hostlist->head; hp; hp = hp->next)
#define FOR_EACH_LOCAL_HOSTPING(hp) \
for (hp = conf_hostping_tail \
? conf_hostping_tail->next \
: hostlist->head; \
hp; \
hp = hp->next)
static void
hostping_extract_stat(HOSTPING *host, struct host_stat *st)
{
memcpy(&st->start_tv, &host->start_tv,
sizeof(host->stat_last.start_tv));
memcpy(&st->stop_tv,
timeval_is_null(&host->recv_tv)
? &host->xmit_tv : &host->recv_tv,
sizeof(host->stat_last.stop_tv));
st->xmit_count = host->xmit_count;
st->recv_count = host->recv_count;
st->dup_count = host->dup_count;
st->tmin = host->tmin;
st->tmax = host->tmax;
if (host->recv_count > 0) {
double total = host->recv_count;
double avg = host->tsum / total;
double vari = host->tsumsq / total - avg * avg;
st->avg = avg;
st->stddev = nsqrt(vari, 0.0005);
}
}
static void
hostping_commit(HOSTPING *host)
{
if (host->stat_last.status != HOST_STAT_VALID) {
hostping_extract_stat(host, &host->stat_last);
host->stat_last.status = HOST_STAT_VALID;
}
}
/* Reset runtime statistics counters */
static void
hostping_reset(HOSTPING *host)
{
hostping_lock(host);
if (host->xmit_count > 0)
hostping_commit(host);
host->xmit_count = 0;
host->recv_count = 0;
host->dup_count = 0;
host->tmin = HOSTPING_TMIN_INIT;
host->tmax = 0;
host->tsum = 0;
host->tsumsq = 0;
memset(host->nreply, 0, ping_count * sizeof(host->nreply[0]));
switch (host->stat_last.status) {
case HOST_STAT_INIT:
break;
case HOST_STAT_VALID:
host->stat_last.status = HOST_STAT_PENDING;
break;
case HOST_STAT_PENDING:
host->stat_last.status = HOST_STAT_INVALID;
break;
case HOST_STAT_INVALID:
break;
}
hostping_unlock(host);
}
static int
get_hostping_stat(HOSTPING *host, struct json_value **retval)
{
struct json_val
|