/* 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 .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ping903.h"
#include "json.h"
#include "defs.h"
unsigned long probe_interval = 60;
unsigned long ping_interval = 1;
unsigned long ping_count = 10;
unsigned long ping_tolerance = 3;
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 = malloc(sizeof(*hp));
if (hp) {
memset(hp, 0, sizeof(*hp));
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;
a->tail = b->head;
a->count += b->count;
b->head = b->tail = NULL;
b->count = 0;
}
static HOSTPING *
hostlist_locate(HOSTLIST *list, char const *name)
{
HOSTPING *hp;
for (hp = list->head; hp; hp++) {
if (strcmp(hp->name, name) == 0)
break;
}
return hp;
}
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);
static void update_commit(void);
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->tmin = host->tmin;
st->tmax = host->tmax;
if (host->recv_count > 0) {
double total = host->recv_count; //FIXME: repeat 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->tmin = 999999999.0;
host->tmax = 0;
host->tsum = 0;
host->tsumsq = 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_value *obj, *v;
struct host_stat const *st;
struct host_stat stbuf;
static char const *host_stat_status[] = {
"init",
"valid",
"pending",
"invalid"
};
int validity;
double ts;
hostping_lock(host);
if (!(obj = json_new_object()))
goto err;
if (!(v = json_new_string(host->name))
|| json_object_set(obj, "name", v))
goto err;
if (host_stat_is_valid(&host->stat_last)) {
st = &host->stat_last;
validity = 1;
} else if (host->stat_last.status == HOST_STAT_INIT
&& host->xmit_count > ping_tolerance) {
hostping_extract_stat(host, &stbuf);
stbuf.status = HOST_STAT_PENDING;
st = &stbuf;
validity = 1;
} else {
st = NULL;
validity = 0;
}
if (!(v = json_new_bool(validity))
|| json_object_set(obj, "validity", v))
goto err;
if (!(v = json_new_string(host_stat_status[host->stat_last.status]))
|| json_object_set(obj, "status", v))
goto err;
ts = timeval_to_double(&host->xmit_tv);
if (!(v = json_new_number(ts))
|| json_object_set(obj, "xmit-timestamp", v))
goto err;
if (st) {
int is_alive = st->xmit_count - st->recv_count
< ping_tolerance;
if (!(v = json_new_string(host_stat_status[st->status]))
|| json_object_set(obj, "status", v))
goto err;
ts = timeval_to_double(&st->start_tv);
if (!(v = json_new_number(ts))
|| json_object_set(obj, "start-timestamp", v))
goto err;
ts = timeval_to_double(&st->stop_tv);
if (!(v = json_new_number(ts))
|| json_object_set(obj, "stop-timestamp", v))
goto err;
if (!(v = json_new_bool(is_alive))
|| json_object_set(obj, "alive", v))
goto err;
if (!(v = json_new_number(st->xmit_count))
|| json_object_set(obj, "xmit", v))
goto err;
if (!(v = json_new_number(st->recv_count))
|| json_object_set(obj, "recv", v))
goto err;
if (!(v = json_new_number((double) 100
* (st->xmit_count - st->recv_count)
/ st->xmit_count))
|| json_object_set(obj, "loss", v))
goto err;
if (st->recv_count > 0) {
if (!(v = json_new_number(st->tmin))
|| json_object_set(obj, "tmin", v))
goto err;
if (!(v = json_new_number(st->tmax))
|| json_object_set(obj, "tmax", v))
goto err;
if (!(v = json_new_number(st->avg))
|| json_object_set(obj, "avg", v))
goto err;
if (!(v = json_new_number(st->stddev))
|| json_object_set(obj, "stddev", v))
goto err;
}
} else {
if (!(v = json_new_string(host_stat_status[host->stat_last.status]))
|| json_object_set(obj, "status", v))
goto err;
}
hostping_unlock(host);
*retval = obj;
return GET_OK;
err:
hostping_unlock(host);
error("out of memory when formatting statistics for %s", host->name);
json_value_free(v);
json_value_free(obj);
return GET_NOMEM;
}
int
get_hostname_stat(char const *name, struct json_value **retval)
{
HOSTPING *hp;
int rc = GET_NOENT;
*retval = NULL;
pthread_rwlock_rdlock(&hostlist_rwlock);
FOR_EACH_HOSTPING(hp) {
if (strcmp(hp->name, name) == 0) {
rc = get_hostping_stat(hp, retval);
break;
}
}
pthread_rwlock_unlock(&hostlist_rwlock);
return rc;
}
int
get_ipaddr_stat(struct sockaddr *sa, int salen, struct json_value **retval)
{
HOSTPING *hp;
int rc = GET_NOENT;
*retval = NULL;
pthread_rwlock_rdlock(&hostlist_rwlock);
FOR_EACH_HOSTPING(hp) {
if (hp->addrlen == salen
&& memcmp(hp->addr, sa, salen) == 0) {
rc = get_hostping_stat(hp, retval);
break;
}
}
pthread_rwlock_unlock(&hostlist_rwlock);
return rc;
}
int
get_all_host_stat(struct json_value **retval)
{
struct json_value *ar;
HOSTPING *hp;
int rc = GET_OK;
if (!(ar = json_new_array()))
return GET_NOMEM;
pthread_rwlock_rdlock(&hostlist_rwlock);
FOR_EACH_HOSTPING(hp) {
struct json_value *v;
if (get_hostping_stat(hp, &v)) {
rc = GET_NOMEM;
break;
}
if (json_array_append(ar, v)) {
json_value_free(v);
rc = GET_NOMEM;
break;
}
}
pthread_rwlock_unlock(&hostlist_rwlock);
if (rc == GET_OK)
*retval = ar;
else
json_value_free(ar);
return rc;
}
int
get_all_hosts(struct json_value **retval)
{
struct json_value *ar;
HOSTPING *hp;
int rc = GET_OK;
if (!(ar = json_new_array()))
return GET_NOMEM;
pthread_rwlock_rdlock(&hostlist_rwlock);
FOR_EACH_HOSTPING(hp) {
struct json_value *jv;
jv = json_new_string(hp->name);
if (!jv) {
rc = GET_NOMEM;
break;
}
if (json_array_append(ar, jv)) {
json_value_free(jv);
rc = GET_NOMEM;
break;
}
}
pthread_rwlock_unlock(&hostlist_rwlock);
if (rc == GET_OK)
*retval = ar;
else
json_value_free(ar);
return rc;
}
int
get_host_matches(struct addrinfo **aip, struct json_value **retval)
{
struct json_value *ar;
HOSTPING *hp;
struct addrinfo *ai_head = NULL, *ai_tail = NULL;
struct addrinfo *ai = *aip;
int ret = -1;
if (!(ar = json_new_array()))
return -1;
pthread_rwlock_rdlock(&hostlist_rwlock);
FOR_EACH_HOSTPING(hp) {
struct addrinfo *prev = NULL, *p;
if (!ai)
break;
p = ai;
while (p) {
struct addrinfo *next = p->ai_next;
if (p->ai_addrlen == hp->addrlen
&& memcmp(hp->addr, p->ai_addr,
hp->addrlen) == 0) {
struct json_value *jv;
jv = json_new_string(hp->name);
if (!jv)
goto err;
if (json_array_append(ar, jv)) {
json_value_free(jv);
goto err;
}
if (prev)
prev->ai_next = next;
else
ai = next;
p->ai_next = NULL;
if (ai_tail)
ai_tail->ai_next = p;
else
ai_head = p;
ai_tail = p;
break;
} else {
prev = p;
p = next;
}
}
}
ret = 0;
*retval = ar;
ar = NULL;
err:
pthread_rwlock_unlock(&hostlist_rwlock);
if (ai_tail) {
ai_tail->ai_next = ai;
*aip = ai_head;
}
json_value_free(ar);
return ret;
}
int
pinger_host_delete_by_name(char const *name)
{
HOSTPING *hp;
int rc;
pthread_mutex_lock(&update_mutex);
if (check_host(name)) {
char *cp = strdup(name);
if (cp) {
if (update_add(UPDATE_DELETE, cp) == 0)
rc = UPD_OK;
else {
rc = UPD_NOMEM;
free(cp);
}
}
} else {
rc = UPD_EXISTS;
}
pthread_mutex_unlock(&update_mutex);
return rc;
}
int
pinger_host_add_name(char const *name)
{
int rc = 0;
struct addrinfo hints, *res;
HOSTPING *hp;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
rc = getaddrinfo(name, NULL, &hints, &res);
if (rc) {
return UPD_NORESOLV;
}
pthread_mutex_lock(&update_mutex);
if (check_host(name) == 0) {
rc = UPD_NOMEM;
hp = hostping_create(name, res->ai_addr, res->ai_addrlen);
if (hp) {
HOSTLIST *hl = hostlist_create();
if (hl) {
hostlist_add(hl, hp);
if (update_add(UPDATE_APPEND, hl) == 0)
rc = UPD_OK;
else
hostlist_free(hl);
} else
hostping_free(hp);
}
} else {
rc = UPD_EXISTS;
}
pthread_mutex_unlock(&update_mutex);
freeaddrinfo(res);
return rc;
}
int
pinger_hostlist_set(struct json_value *obj, char const **err_text,
int *err_pos)
{
int mode = UPDATE_APPEND;
struct json_value *ar;
HOSTLIST *tmp = NULL;
HOSTPING *hp;
struct addrinfo hints;
size_t i;
int ret;
#define RETERR(status,text,pos) do {\
*err_text = text; \
*err_pos = pos; \
ret = status; \
goto err; \
} while (0)
if (!obj)
RETERR(MHD_HTTP_BAD_REQUEST, "invalid object", 0);
if (obj->type == json_object) {
struct json_value *jv;
if (json_object_get(obj, "mode", &jv)) {
RETERR(MHD_HTTP_BAD_REQUEST,
"\"mode\" attribute missing",
0);
}
if (strcmp(jv->v.s, "append") == 0)
mode = UPDATE_APPEND;
else if (strcmp(jv->v.s, "replace") == 0)
mode = UPDATE_REPLACE;
else
RETERR(MHD_HTTP_BAD_REQUEST, "\"mode\": invalid value",
0);
if (json_object_get(obj, "ip-list", &ar))
RETERR(MHD_HTTP_BAD_REQUEST,
"\"ip-list\" attribute missing",
0);
} else if (obj->type == json_array) {
mode = UPDATE_APPEND;
ar = obj;
} else {
RETERR(MHD_HTTP_BAD_REQUEST, "bad object type", 0);
}
tmp = hostlist_create();
if (!tmp)
RETERR(MHD_HTTP_INTERNAL_SERVER_ERROR,NULL,0);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_protocol = IPPROTO_TCP;
for (i = 0; i < ar->v.a->oc; i++) {
struct addrinfo *res;
int rc;
struct json_value *jv;
if (json_array_get(ar, i, &jv) || jv->type != json_string)
RETERR(MHD_HTTP_BAD_REQUEST,
"bad object type", i + 1);
rc = getaddrinfo(jv->v.s, NULL, &hints, &res);
if (rc)
RETERR(MHD_HTTP_BAD_REQUEST,
"host name does not resolve", i + 1);
hp = hostping_create(jv->v.s, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
if (!hp)
RETERR(MHD_HTTP_INTERNAL_SERVER_ERROR, NULL, i + 1);
hostlist_add(tmp, hp);
}
if (update_add(mode, tmp)) {
RETERR(MHD_HTTP_INTERNAL_SERVER_ERROR, NULL, 0);
} else {
*err_text = NULL;
*err_pos = 0;
return MHD_HTTP_OK;
}
err:
hostlist_free(tmp);
return ret;
}
typedef struct update_entry {
enum update_type type;
struct update_entry *next;
union {
void *ptr;
HOSTLIST *hlist;
char *name;
} v;
} UPDATE_ENTRY;
static UPDATE_ENTRY *update_head, *update_tail;
static void save_local_ip_list(void);
static int
update_add(UPDATE_TYPE t, void *data)
{
UPDATE_ENTRY *uent = malloc(sizeof(*uent));
if (!uent)
return -1;
uent->type = t;
uent->v.ptr = data;
uent->next = NULL;
if (uent->type == UPDATE_REPLACE) {
while (update_head) {
UPDATE_ENTRY *next = update_head->next;
switch (update_head->type) {
case UPDATE_APPEND:
case UPDATE_REPLACE:
hostlist_free(update_head->v.hlist);
break;
case UPDATE_DELETE:
free(update_head->v.name);
}
free(update_head);
update_head = next;
}
update_tail = NULL;
}
if (update_tail)
update_tail->next = uent;
else
update_head = uent;
update_tail = uent;
return 0;
}
static void
update_commit(void)
{
HOSTPING *hp;
int upd;
pthread_mutex_lock(&update_mutex);
pthread_rwlock_wrlock(&hostlist_rwlock);
upd = update_head != NULL;
while (update_head) {
UPDATE_ENTRY *next = update_head->next;
switch (update_head->type) {
case UPDATE_APPEND:
hostlist_concat(hostlist, update_head->v.hlist);
hostlist_free(update_head->v.hlist);
break;
case UPDATE_REPLACE:
if (conf_hostping_tail) {
HOSTLIST *tmp = update_head->v.hlist;
hp = conf_hostping_tail->next;
if (tmp->head)
tmp->head->prev = conf_hostping_tail;
conf_hostping_tail->next = tmp->head;
hostlist->tail = tmp->tail;
hostlist->count += tmp->count;
while (hp) {
HOSTPING *next = hp->next;
hostping_free(hp);
hp = next;
hostlist->count--;
}
free(tmp);
} else {
hostlist_free(hostlist);
hostlist = update_head->v.hlist;
}
break;
case UPDATE_DELETE:
FOR_EACH_LOCAL_HOSTPING(hp) {
if (strcmp(hp->name, update_head->v.name) == 0) {
hostlist_remove(hostlist, hp);
hostping_free(hp);
break;
}
}
free(update_head->v.name);
}
free(update_head);
update_head = next;
}
update_tail = NULL;
if (upd)
save_local_ip_list();
pthread_rwlock_unlock(&hostlist_rwlock);
pthread_mutex_unlock(&update_mutex);
}
static int
check_host(char const *name)
{
HOSTPING *hp;
UPDATE_ENTRY *uent;
int found;
FOR_EACH_HOSTPING(hp) {
if (strcmp(hp->name, name) == 0)
return 1;
}
found = 0;
for (uent = update_head; uent; uent = uent->next) {
switch (update_head->type) {
case UPDATE_APPEND:
case UPDATE_REPLACE:
for (hp = uent->v.hlist->head; hp; hp = hp->next) {
if (strcmp(hp->name, name) == 0) {
found++;
break;
}
}
break;
case UPDATE_DELETE:
if (strcmp(uent->v.name, name) == 0)
found--;
}
}
return found;
}
static int ping_fd;
static pthread_mutex_t sendq_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t sendq_cond = PTHREAD_COND_INITIALIZER;
static int send_p;
static unsigned xmit_total;
static unsigned recv_total;
#define ICMP_HEADER_LEN (offsetof(struct icmp, icmp_data))
#define PING_DATALEN (64 - ICMP_HEADER_LEN)
size_t data_length = PING_DATALEN;
static unsigned char *data_buffer;
static int ping_ident;
#define MAX_PING_TIMEOUT 10
enum {
MAX_SEQNO = USHRT_MAX,
MOD_SEQNO = MAX_SEQNO + 1
};
struct seqidx {
HOSTPING *host;
struct timeval tv;
int replied;
};
static struct seqidx *seqidx;
static unsigned short next_seqno;
static int
seqno_alloc(HOSTPING *addr, struct timeval *tv)
{
int n = next_seqno;
do {
if (tv->tv_sec - seqidx[n].tv.tv_sec > MAX_PING_TIMEOUT) {
memcpy(&seqidx[n].tv, tv, sizeof(*tv));
seqidx[n].host = addr;
seqidx[n].replied = 0;
next_seqno = (n + 1) % MOD_SEQNO;
return n;
}
n = (n + 1) % MOD_SEQNO;
} while (n != next_seqno);
error("no free sequence numbers");
return -1;
}
static HOSTPING *
hostping_from_seqno(int seq)
{
HOSTPING *host = seqidx[seq].host;
if (seqidx[seq].replied)
info("%s:%d: DUP!", host->name, seq);
seqidx[seq].replied++;
hostping_lock(host);
return host;
}
void
p903_init(void)
{
struct protoent *proto;
int i;
FILE *fp;
conf_hostping_tail = hostlist->tail;
fp = fopen(LOCAL_IP_LIST_FILE, "r");
if (fp) {
int rc = file_read_ip_list(fp, LOCAL_IP_LIST_FILE);
if (rc == CF_RET_FAIL)
exit(1);
fclose(fp);
} else if (errno != ENOENT) {
fatal("can't open %s: %s", LOCAL_IP_LIST_FILE,
strerror(errno));
exit(1);
}
if (hostlist->count == 0) {
info("no IP addresses configured, starting anyway");
}
proto = getprotobyname("icmp");
if (!proto) {
fatal("no entry for icmp in the system protocol database");
exit(1);
}
ping_fd = socket(AF_INET, SOCK_RAW, proto->p_proto);
if (ping_fd < 0) {
fatal("can't create ICMP socket: %s", strerror(errno));
exit(1);
}
ping_ident = getpid() & 0xffff;
data_buffer = emalloc(data_length);
for (i = 0; i < data_length; i++)
data_buffer[i] = i;
seqidx = calloc(MAX_SEQNO + 1, sizeof(seqidx[0]));
if (!seqidx)
emalloc_die();
}
static unsigned short
icmp_cksum(unsigned char * addr, int len)
{
register int sum = 0;
unsigned short answer = 0;
unsigned short *wp;
for (wp = (unsigned short *) addr; len > 1; wp++, len -= 2)
sum += *wp;
/* Take in an odd byte if present */
if (len == 1) {
*(unsigned char *) & answer = *(unsigned char *) wp;
sum += answer;
}
sum = (sum >> 16) + (sum & 0xffff); /* add high 16 to low 16 */
sum += (sum >> 16); /* add carry */
answer = ~sum; /* truncate to 16 bits */
return answer;
}
static int
icmp_generic_encode(unsigned char * buffer, size_t bufsize, int type,
int ident, int seqno)
{
struct icmp *icmp;
if (bufsize < ICMP_MINLEN)
return -1;
icmp = (struct icmp *) buffer;
icmp->icmp_type = type;
icmp->icmp_code = 0;
icmp->icmp_cksum = 0;
icmp->icmp_seq = htons(seqno);
icmp->icmp_id = htons(ident);
icmp->icmp_cksum = icmp_cksum(buffer, bufsize);
return 0;
}
static int
icmp_generic_decode(unsigned char * buffer, size_t bufsize,
struct ip **ipp, struct icmp ** icmpp)
{
size_t hlen;
unsigned short cksum;
struct ip *ip;
struct icmp *icmp;
/* IP header */
ip = (struct ip *) buffer;
hlen = ip->ip_hl << 2;
if (bufsize < hlen + ICMP_MINLEN)
return -1;
/* ICMP header */
icmp = (struct icmp *) (buffer + hlen);
/* Prepare return values */
*ipp = ip;
*icmpp = icmp;
/* Recompute checksum */
cksum = icmp->icmp_cksum;
icmp->icmp_cksum = 0;
icmp->icmp_cksum = icmp_cksum((unsigned char *) icmp, bufsize - hlen);
if (icmp->icmp_cksum != cksum)
return 1;
icmp->icmp_seq = ntohs(icmp->icmp_seq);
icmp->icmp_id = ntohs(icmp->icmp_id);
return 0;
}
static void
send_echo(HOSTPING *host, unsigned char *ping_buffer)
{
struct icmp *icmp = (struct icmp *) ping_buffer;
size_t buflen;
ssize_t n;
int seqno;
hostping_lock(host);
gettimeofday(&host->xmit_tv, NULL);
hostping_unlock(host);
memcpy(icmp->icmp_data, &host->xmit_tv, sizeof(host->xmit_tv));
if (data_buffer)
memcpy(icmp->icmp_data + sizeof(host->xmit_tv), data_buffer,
data_length - sizeof(host->xmit_tv));
buflen = ICMP_HEADER_LEN + data_length;
seqno = seqno_alloc(host, &host->xmit_tv);
if (seqno == -1) {
//FIXME
return;
}
if (verbose > 2)
info("sending %d bytes to %s, icmp_seq=%d", buflen, host->name,
seqno);
icmp_generic_encode(ping_buffer, buflen, ICMP_ECHO, ping_ident, seqno);
n = sendto(ping_fd, (char *) ping_buffer, buflen, 0,
host->addr, host->addrlen);
if (n < 0) {
error("%s: sendto: %s", host->name, strerror(errno));
} else {
hostping_lock(host);
if (host->xmit_count == 0)
host->start_tv = host->xmit_tv;
host->xmit_count++;
hostping_unlock(host);
xmit_total++;
if (n != buflen)
error("ping: wrote %s %d chars, ret=%d\n",
host->name, buflen, n);
}
}
static void hostping_commit(HOSTPING *host);
void *
p903_sender(void *p)
{
size_t i;
unsigned char *ping_buffer;
struct pollfd pfd;
int n;
unsigned send_count = 0;
double d;
struct timespec delay;
pfd.fd = ping_fd;
pfd.events = POLLOUT;
ping_buffer = emalloc(sizeof(struct icmp) + data_length);
d = (double) ping_interval / hostlist->count;
delay.tv_sec = (int) d;
delay.tv_nsec = (d - delay.tv_sec) * NANOSEC_IN_SEC;
pthread_mutex_lock(&sendq_mutex);
while (1) {
struct timespec ts;
HOSTPING *hp;
if (!send_p) {
while (!send_p)
pthread_cond_wait(&sendq_cond, &sendq_mutex);
send_count = 0;
}
clock_gettime(CLOCK_MONOTONIC, &ts);
FOR_EACH_HOSTPING(hp) {
struct timespec nts;
clock_gettime(CLOCK_MONOTONIC, &nts);
timespec_incr(&nts, &delay);
n = poll(&pfd, 1, -1);
if (n == 1) {
send_echo(hp, ping_buffer);
} else if (n == -1) {
fatal("poll: %s", strerror(errno));
exit(1);
}
if (i < hostlist->count - 1)
clock_nanosleep(CLOCK_MONOTONIC,
TIMER_ABSTIME, &nts,
NULL);
}
send_count++;
if (send_count == ping_count)
send_p = 0;
else {
ts.tv_sec += ping_interval;
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts,
NULL);
}
}
pthread_mutex_unlock(&sendq_mutex);
return NULL;
}
static void
log_echo(struct sockaddr *addr, socklen_t addrlen,
struct icmp *icmp, struct ip *ip,
size_t datalen, double rtt)
{
char hbuf[NI_MAXHOST];
datalen -= ip->ip_hl << 2;
if (getnameinfo(addr, addrlen, hbuf, sizeof(hbuf),
NULL, 0, NI_NUMERICHOST)) {
strcpy(hbuf, "UNKNOWN");
}
info("%d bytes from %s: icmp_seq=%u ttl=%d time=%.3f ms", datalen,
hbuf, icmp->icmp_seq, ip->ip_ttl, rtt);
}
void *
p903_receiver(void *p)
{
size_t buflen = sizeof(struct icmp) + data_length;
unsigned char *ping_buffer;
ping_buffer = emalloc(buflen);
while (1) {
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
ssize_t n;
int rc;
struct icmp *icmp;
struct ip *ip;
HOSTPING *host;
n = recvfrom(ping_fd, ping_buffer, buflen, 0,
(struct sockaddr *) &addr, &addrlen);
if (n < 0) {
error("recvfrom: %s", strerror(errno));
continue;
}
rc = icmp_generic_decode(ping_buffer, n, &ip, &icmp);
if (rc < 0) {
char hbuf[NI_MAXHOST];
if (getnameinfo((struct sockaddr *) &addr,
addrlen,
hbuf, sizeof(hbuf),
NULL, 0, NI_NUMERICHOST)) {
error("packet too short (%d bytes)", n);
} else {
error("packet too short (%d bytes), from %s",
n, hbuf);
}
continue;
}
if (icmp->icmp_type != ICMP_ECHOREPLY
|| icmp->icmp_id != ping_ident)
continue;
recv_total++;
if (rc) {
char hbuf[NI_MAXHOST];
if (getnameinfo((struct sockaddr *) &addr,
addrlen,
hbuf, sizeof(hbuf),
NULL, 0, NI_NUMERICHOST)) {
error("checksum mismatch");
} else {
error("checksum mismatch from %s", hbuf);
}
continue;
}
host = hostping_from_seqno(icmp->icmp_seq);
if (host) {
struct timeval tv_orig, tv_diff, *tp;
double rtt;
gettimeofday(&host->recv_tv, NULL);
tp = (struct timeval *) icmp->icmp_data;
/* Avoid unaligned data: */
memcpy(&tv_orig, tp, sizeof (tv_orig));
timersub(&host->recv_tv, &tv_orig, &tv_diff);
rtt = timeval_to_double(&tv_diff) * 1000.0;
host->tsum += rtt;
host->tsumsq += rtt * rtt;
if (rtt < host->tmin)
host->tmin = rtt;
if (rtt > host->tmax)
host->tmax = rtt;
host->recv_count++;
if (verbose > 1)
log_echo((struct sockaddr *)&addr, addrlen,
icmp, ip, n, rtt);
if (host->recv_count == ping_count)
hostping_commit(host);
hostping_unlock(host);
} else
fatal("no host found for sequence number %d",
icmp->icmp_seq);
}
}
static int
create_dirs(char const *filename)
{
char *namebuf;
char *p;
char *endp;
struct stat st;
size_t len;
p = strrchr(filename, '/');
if (!p)
return -1;
len = p - filename;
namebuf = malloc(len + 1);
if (!namebuf)
return -1;
memcpy(namebuf, filename, len);
namebuf[len] = 0;
endp = namebuf + len;
while ((p = strrchr(namebuf, '/')) != NULL && p > namebuf) {
*p = 0;
if (stat(namebuf, &st)) {
if (errno == ENOENT)
continue;
error("can't stat %s: %s", namebuf, strerror(errno));
goto err;
}
if (S_ISDIR(st.st_mode)) {
break;
} else {
error("file name component %s is not a directory",
namebuf);
goto err;
}
}
while (p < endp) {
*p = '/';
if (mkdir(namebuf, 0755)) {
error("can't create directory %s: %s",
namebuf, strerror(errno));
goto err;
}
p = p + strlen(p);
}
return 0;
err:
free(namebuf);
return -1;
}
static void
save_local_ip_list(void)
{
FILE *fp;
HOSTPING *hp;
size_t i;
info("saving mutable IP address list");
fp = fopen(LOCAL_IP_LIST_FILE, "w");
if (!fp) {
if (errno == ENOENT) {
create_dirs(LOCAL_IP_LIST_FILE);
fp = fopen(LOCAL_IP_LIST_FILE, "w");
}
if (!fp) {
fatal("can't open %s for writing: %s",
LOCAL_IP_LIST_FILE,
strerror(errno));
return;
}
}
FOR_EACH_LOCAL_HOSTPING(hp) {
fprintf(fp, "%s\n", hp->name);
}
fclose(fp);
}
void *
p903_scheduler(void *p)
{
while (1) {
HOSTPING *hp;
pthread_mutex_lock(&sendq_mutex);
/* Reset all statistics */
FOR_EACH_HOSTPING(hp) {
hostping_reset(hp);
}
/* Commit updates */
update_commit();
send_p = 1;
pthread_cond_broadcast(&sendq_cond);
pthread_mutex_unlock(&sendq_mutex);
sleep(probe_interval);
if (verbose)
info("total sent=%u, received=%u",
xmit_total, recv_total);
xmit_total = recv_total = 0;
}
}
void *
p903_saver(void *p)
{
update_commit();
return NULL;
}