/* This file is part of Ping903 Copyright (C) 2020-2023 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 #define _GNU_SOURCE #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" /* 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 *immutable_hostping_tail; static RBT_TREE *host_name_tree, *host_addr_tree; 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; enum { CHECK_HOST_NOT_FOUND, CHECK_HOST_FOUND, CHECK_HOST_IMMUTABLE }; static int check_host(char const *name, struct sockaddr *addr, socklen_t len); static int update_add(UPDATE_TYPE t, void *data); static int host_name_cmp(HOSTPING *a, HOSTPING *b) { return strcmp(a->name, b->name); } static int host_addr_cmp(HOSTPING *a, HOSTPING *b) { if (a->addrlen != b->addrlen) return a->addrlen - b->addrlen; return memcmp(a->addr, b->addr, b->addrlen); } void pinger_setup(void) { hostlist = hostlist_create(); if (!hostlist) emalloc_die(); host_name_tree = rbt_tree_create(host_name_cmp); if (!host_name_tree) emalloc_die(); host_addr_tree = rbt_tree_create(host_addr_cmp); if (!host_addr_tree) emalloc_die(); } RBT_LOOKUP_RESULT pinger_rbt_insert(HOSTPING *hp) { RBT_LOOKUP_RESULT res; res = rbt_insert(host_name_tree, hp); if (res != RBT_LOOKUP_NOENT) return res; res = rbt_insert(host_addr_tree, hp); if (res != RBT_LOOKUP_NOENT) rbt_delete(host_name_tree, hp); return res; } void pinger_rbt_delete(HOSTPING *hp) { rbt_delete(host_name_tree, hp); rbt_delete(host_addr_tree, hp); } static inline HOSTPING * hostping_lookup_by_name(char const *name) { HOSTPING key; key.name = (char*) name; return rbt_lookup(host_name_tree, &key); } static inline HOSTPING * hostping_lookup_by_addr(struct sockaddr *addr, socklen_t addrlen) { HOSTPING key; if (!addr) return NULL; key.addr = addr; key.addrlen = addrlen; return rbt_lookup(host_addr_tree, &key); } RBT_LOOKUP_RESULT pinger_host_add(char const *name, struct sockaddr *addr, socklen_t addrlen) { RBT_LOOKUP_RESULT res; HOSTPING *hp; hp = hostping_create(name, addr, addrlen); if (!hp) return RBT_LOOKUP_FAILURE; res = pinger_rbt_insert(hp); if (res == RBT_LOOKUP_NOENT) hostlist_add(hostlist, hp); else hostping_free(hp); return res; } #define FOR_EACH_HOSTPING(hp) for (hp = hostlist->head; hp; hp = hp->next) #define FOR_EACH_MUTABLE_HOSTPING(hp) \ for (hp = immutable_hostping_tail \ ? immutable_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_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(st->dup_count)) || json_object_set(obj, "dup", 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); hp = hostping_lookup_by_name(name); if (hp) rc = get_hostping_stat(hp, retval); 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 json_value *ar) { struct addrent { char const *name; struct addrinfo *res; }; size_t count = json_array_length(ar); size_t i; struct addrinfo hints; int errors = 0; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_protocol = IPPROTO_TCP; pthread_rwlock_rdlock(&hostlist_rwlock); for (i = 0; !errors && i < count; i++) { char const *name; int rc; struct addrinfo *res, *rp; struct json_value *obj, *jv, *var; HOSTPING *hp; json_array_get(ar, i, &obj); json_object_get(obj, "name", &jv); name = jv->v.s; rc = getaddrinfo(name, NULL, &hints, &res); switch (rc) { case 0: break; case EAI_FAIL: #ifdef EAI_NODATA case EAI_NODATA: #endif case EAI_NONAME: if (!(jv = json_new_string("Doesn't resolve")) || json_object_set(obj, "error", jv)) { json_value_free(jv); } errors = 1; break; case EAI_MEMORY: errors = 1; break; case EAI_SYSTEM: error("Internal error resolving %s: %s", name, strerror(errno)); errors = 1; break; default: error("Internal error resolving %s: %s", name, gai_strerror(rc)); errors = 1; break; } if (errors) break; json_object_get(obj, "hosts", &var); hp = hostping_lookup_by_name(name); if (hp) { if (!(jv = json_new_string(hp->name)) || json_array_append(var, jv)) { json_value_free(jv); errors = 1; } } for (rp = res; !errors && rp; rp = rp->ai_next) { hp = hostping_lookup_by_addr(rp->ai_addr, rp->ai_addrlen); if (hp) { if (!(jv = json_new_string(hp->name)) || json_array_append(var, jv)) { json_value_free(jv); errors = 1; } } } freeaddrinfo(res); } pthread_rwlock_unlock(&hostlist_rwlock); return errors; } int pinger_host_delete_by_name(char const *name) { int rc; pthread_mutex_lock(&update_mutex); if (check_host(name, NULL, 0) == CHECK_HOST_FOUND) { 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, res->ai_addr, res->ai_addrlen) == CHECK_HOST_NOT_FOUND) { 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); switch (check_host(jv->v.s, res->ai_addr, res->ai_addrlen)) { case CHECK_HOST_NOT_FOUND: /* Safe to add */ break; case CHECK_HOST_FOUND: if (mode == UPDATE_REPLACE) { /* It's OK in replace mode */ break; } /* fall through */ case CHECK_HOST_IMMUTABLE: freeaddrinfo(res); RETERR(MHD_HTTP_BAD_REQUEST, "host name or IP address already exists", 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_mutable_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 hostlist_copy_stat(HOSTLIST *hl) { HOSTPING *hp; for (hp = hl->head; hp; hp = hp->next) { HOSTPING *cur = hostping_lookup_by_name(hp->name); if (cur) hp->stat_last = cur->stat_last; } } static void hostlist_index(HOSTLIST *hl) { HOSTPING *hp; for (hp = hl->head; hp; hp = hp->next) { pinger_rbt_insert(hp); //FIXME: Handle RBT_LOOKUP_FAILURE (out of memory) } } static void hostlist_unindex(HOSTLIST *hl) { HOSTPING *hp; for (hp = hl->head; hp; hp = hp->next) { pinger_rbt_delete(hp); } } void p903_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_index(update_head->v.hlist); hostlist_concat(hostlist, update_head->v.hlist); hostlist_free(update_head->v.hlist); break; case UPDATE_REPLACE: hostlist_copy_stat(update_head->v.hlist); if (immutable_hostping_tail) { HOSTLIST *tmp = update_head->v.hlist; hp = immutable_hostping_tail->next; if (tmp->head) tmp->head->prev = immutable_hostping_tail; immutable_hostping_tail->next = tmp->head; hostlist->tail = tmp->tail; hostlist->count += tmp->count; while (hp) { HOSTPING *next = hp->next; pinger_rbt_delete(hp); hostping_free(hp); hp = next; hostlist->count--; } hostlist_index(tmp); free(tmp); } else { hostlist_unindex(hostlist); hostlist_free(hostlist); hostlist = update_head->v.hlist; hostlist_index(hostlist); } break; case UPDATE_DELETE: hp = hostping_lookup_by_name(update_head->v.name); if (hp) { hostlist_remove(hostlist, hp); pinger_rbt_delete(hp); hostping_free(hp); } free(update_head->v.name); } free(update_head); update_head = next; } update_tail = NULL; if (upd) save_mutable_ip_list(); pthread_rwlock_unlock(&hostlist_rwlock); pthread_mutex_unlock(&update_mutex); } /* * Check if the given host is already in the hostlist. Look for * matching name or, if specified, address. * Return value: * CHECK_HOST_NOT_FOUND - Host not found. * CHECK_HOST_FOUND - Host found. * CHECK_HOST_IMMUTABLE - Host found and is immutable. */ static int check_host(char const *name, struct sockaddr *addr, socklen_t len) { HOSTPING *hp; UPDATE_ENTRY *uent; int found; hp = hostping_lookup_by_name(name); if (!hp) hp = hostping_lookup_by_addr(addr, len); if (hp) { if (hp->immutable) return CHECK_HOST_IMMUTABLE; found = 1; } else found = 0; for (uent = update_head; uent; uent = uent->next) { switch (uent->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 > 0 ? CHECK_HOST_FOUND : CHECK_HOST_NOT_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 long probe_num; /* Ordinal number of the current probe. */ /* Totals for the current probe: */ static unsigned xmit_total; /* Number of requests transmitted. */ static unsigned recv_total; /* Number of replies received. */ #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; /* Ping identifier (for the icmp_id member of struct icmp) */ static int ping_ident; /* Constants for sequence number database management */ /* Max. time in seconds after which a sequence database entry can be reused. */ #define MAX_PING_TIMEOUT 10 enum { MAX_SEQNO = USHRT_MAX, /* Max. value for the sequence number */ MOD_SEQNO = MAX_SEQNO + 1 /* Modulus for computing next sequence number. */ }; /* Sequence number index entry. */ struct seqidx { HOSTPING *host; /* Associated host. */ struct timeval tv; /* Time the echo was sent. */ unsigned long probe_num; /* Number of the probe within which the echo was sent. */ int ping_num; /* Number of echo request within the probe. */ }; static struct seqidx *seqidx; /* Sequence number database. */ static unsigned short next_seqno; /* Next sequence number. */ /* Protect simultaneous access to seqidx. */ static pthread_mutex_t seqno_mutex = PTHREAD_MUTEX_INITIALIZER; /* Allocate and return the sequence number for the given host and * transmission time. * Return negative value if the number cannot be allocated. */ static int seqno_alloc(HOSTPING *host, 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 = host; seqidx[n].probe_num = probe_num; seqidx[n].ping_num = host->xmit_count; next_seqno = (n + 1) % MOD_SEQNO; return n; } n = (n + 1) % MOD_SEQNO; } while (n != next_seqno); error("no free sequence numbers"); return -1; } /* Check the validity of the echo reply SEQ. Return 0 if the reply is * valid, -1 otherwise. * This function is called when both the seqidx array and the HOSTPING * structure associated with SEQ are locked. */ static int check_reply(int seq) { int n = seqidx[seq].ping_num; HOSTPING *host = seqidx[seq].host; if (seqidx[seq].probe_num != probe_num) { /* Case 1. * A latecomer reply, which arrived after its probe round * was committed (see hostping_commit). */ if (verbose > 1) info("%s: reply for discarded echo request #%d, " "seqno %d; probe_num=%lu, current=%lu", host->name, n, seq, seqidx[seq].probe_num, probe_num); } else if (host->xmit_count == 0) { /* Case 2. * A reply came while no echo requests were transmitted yet. */ info("%s: stray reply #%d, seqno %d; probe_num=%lu", host->name, n, seq, probe_num); } else if (n >= 0 && n < ping_count) { if (n > host->xmit_count) { /* Case 3. * Similar to 2, except that some echoes were sent. */ error("%s: phantom reply #%d, seqno %d; xmit_count=%lu", host->name, n, seq, host->xmit_count); } else if (++host->nreply[n] > 1) { /* Case 4. * Duplicate reply. */ host->dup_count++; info("%s: duplicate reply for echo #%d, seqno %d", host->name, n, seq); } else if (host->recv_count == host->xmit_count) { /* Case 5. * Similar to 2 and 3. * Each echo request was replied to, and yet another * reply arrived, which is not a duplicate. */ error("%s: unexpected reply #%d, seqno %d; " "xmit_count=recv_count=%lu", host->name, n, seq, host->xmit_count); } /* Case 6. * This is a valid reply. */ return 0; } else { /* Case 7. * A reply with impossible echo request number. * This one should not happen indeed. */ error("%s: reply for unregistered echo #%d, seqno %d", host->name, n, seq); } return -1; } /* Given an echo sequence number, return the locked HOSTPING structure * associated with this echo request, or NULL if the reply is * invalid. */ static HOSTPING * hostping_from_seqno(int seq) { HOSTPING *host; pthread_mutex_lock(&seqno_mutex); host = seqidx[seq].host; if (host) { hostping_lock(host); if (check_reply(seq)) { hostping_unlock(host); host = NULL; } } else fatal("no host found for sequence number %d", seq); pthread_mutex_unlock(&seqno_mutex); return host; } void p903_init(void) { struct protoent *proto; int i; FILE *fp; HOSTPING *hp; /* Mark all hosts configured so far as immutable and mark the end of the immutable hostlist segment. */ for (hp = hostlist->head; hp; hp = hp->next) hp->immutable = 1; immutable_hostping_tail = hostlist->tail; /* Read in mutable IP list. */ fp = fopen(MUTABLE_IP_LIST_FILE, "r"); if (fp) { int rc = file_read_ip_list(fp, MUTABLE_IP_LIST_FILE); if (rc == CF_RET_FAIL) exit(1); fclose(fp); } else if (errno != ENOENT) { fatal("can't open %s: %s", MUTABLE_IP_LIST_FILE, strerror(errno)); exit(1); } if (hostlist->count == 0) { info("no IP addresses configured, starting anyway"); } /* Open ICMP socket */ 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); } /* Initialize ICMP identifier. */ ping_ident = getpid() & 0xffff; /* Initialize payload buffer */ data_buffer = emalloc(data_length); for (i = 0; i < data_length; i++) data_buffer[i] = i; /* Initialize sequence number database. */ 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); icmp->icmp_seq = ntohs(icmp->icmp_seq); icmp->icmp_id = ntohs(icmp->icmp_id); if (icmp->icmp_cksum != cksum) return 1; 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; pthread_mutex_lock(&seqno_mutex); hostping_lock(host); gettimeofday(&host->xmit_tv, NULL); seqno = seqno_alloc(host, &host->xmit_tv); pthread_mutex_unlock(&seqno_mutex); if (seqno >= 0) { 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; if (verbose > 3) info("sending %zu 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 { if (host->xmit_count == 0) host->start_tv = host->xmit_tv; host->xmit_count++; xmit_total++; if (n != buflen) error("ping: wrote %s %zu chars, ret=%ld\n", host->name, buflen, n); } } else { error("can't allocate sequence number for %s", host->name); } hostping_unlock(host); } static void hostping_commit(HOSTPING *host); void * p903_sender(void *p) { 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 (hp->next) 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("%zu 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 (%zu bytes)", n); } else { error("packet too short (%zu 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 > 2) log_echo((struct sockaddr *)&addr, addrlen, icmp, ip, n, rtt); if (host->recv_count == ping_count) hostping_commit(host); hostping_unlock(host); } } } 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_mutable_ip_list(void) { FILE *fp; HOSTPING *hp; info("saving mutable IP address list"); fp = fopen(MUTABLE_IP_LIST_FILE, "w"); if (!fp) { if (errno == ENOENT) { create_dirs(MUTABLE_IP_LIST_FILE); fp = fopen(MUTABLE_IP_LIST_FILE, "w"); } if (!fp) { fatal("can't open %s for writing: %s", MUTABLE_IP_LIST_FILE, strerror(errno)); return; } } FOR_EACH_MUTABLE_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 */ p903_update_commit(); probe_num++; 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; } }