/* 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; }