1 /**
2  * Object oriented C module to send ICMP and ICMPv6 `echo's.
3  * Copyright (C) 2006-2014  Florian octo Forster <ff at octo.it>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; only version 2 of the License is
8  * applicable.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
18  */
19 
20 #if HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 
24 #if STDC_HEADERS
25 # include <stdlib.h>
26 # include <stdio.h>
27 # include <string.h>
28 # include <stdint.h>
29 # include <inttypes.h>
30 # include <errno.h>
31 # include <assert.h>
32 #else
33 # error "You don't have the standard C99 header files installed"
34 #endif /* STDC_HEADERS */
35 
36 #if HAVE_UNISTD_H
37 # include <unistd.h>
38 #endif
39 
40 #if HAVE_MATH_H
41 # include <math.h>
42 #endif
43 
44 #if TIME_WITH_SYS_TIME
45 # include <sys/time.h>
46 # include <time.h>
47 #else
48 # if HAVE_SYS_TIME_H
49 #  include <sys/time.h>
50 # else
51 #  include <time.h>
52 # endif
53 #endif
54 
55 #if HAVE_SYS_SOCKET_H
56 # include <sys/socket.h>
57 #endif
58 #if HAVE_NETINET_IN_H
59 # include <netinet/in.h>
60 #endif
61 #if HAVE_NETINET_IP_H
62 # include <netinet/ip.h>
63 #endif
64 
65 #if HAVE_NETDB_H
66 # include <netdb.h> /* NI_MAXHOST */
67 #endif
68 
69 #if HAVE_SIGNAL_H
70 # include <signal.h>
71 #endif
72 
73 #if HAVE_SYS_TYPES_H
74 #include <sys/types.h>
75 #endif
76 
77 #include <locale.h>
78 #include <langinfo.h>
79 
80 #if USE_NCURSES
81 # define NCURSES_OPAQUE 1
82 /* http://newsgroups.derkeiler.com/Archive/Rec/rec.games.roguelike.development/2010-09/msg00050.html */
83 # define _X_OPEN_SOURCE_EXTENDED
84 
85 # if HAVE_NCURSESW_NCURSES_H
86 #  include <ncursesw/ncurses.h>
87 # elif HAVE_NCURSES_H
88 #  include <ncurses.h>
89 # endif
90 
91 # define OPING_GREEN 1
92 # define OPING_YELLOW 2
93 # define OPING_RED 3
94 # define OPING_GREEN_HIST 4
95 # define OPING_YELLOW_HIST 5
96 # define OPING_RED_HIST 6
97 
98 double const threshold_green = 0.8;
99 double const threshold_yellow = 0.95;
100 
101 static char const * const hist_symbols_utf8[] = {
102 	"▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" };
103 static size_t const hist_symbols_utf8_num = sizeof (hist_symbols_utf8)
104 	/ sizeof (hist_symbols_utf8[0]);
105 
106 /* scancodes for 6 levels of horizontal bars, ncurses-specific */
107 /* those are not the usual constants because those are not constant */
108 static int const hist_symbols_acs[] = {
109 	115, /* ACS_S9 "⎽" */
110 	114, /* ACS_S7 "⎼" */
111 	113, /* ACS_S5 "─" */
112 	112, /* ACS_S3 "⎻" */
113 	111  /* ACS_S1 "⎺" */
114 };
115 static size_t const hist_symbols_acs_num = sizeof (hist_symbols_acs)
116 	/ sizeof (hist_symbols_acs[0]);
117 
118 /* use different colors without a background for scancodes */
119 static int const hist_colors_utf8[] = {
120 	OPING_GREEN_HIST, OPING_YELLOW_HIST, OPING_RED_HIST };
121 static int const hist_colors_acs[] = {
122 	OPING_GREEN, OPING_YELLOW, OPING_RED };
123 /* assuming that both arrays are the same size */
124 static size_t const hist_colors_num = sizeof (hist_colors_utf8)
125 	/ sizeof (hist_colors_utf8[0]);
126 #endif
127 
128 /* "─" */
129 #define BOXPLOT_WHISKER_BAR       (113 | A_ALTCHARSET)
130 /* "├" */
131 #define BOXPLOT_WHISKER_LEFT_END  (116 | A_ALTCHARSET)
132 /* "┤" */
133 #define BOXPLOT_WHISKER_RIGHT_END (117 | A_ALTCHARSET)
134 /* Inverted */
135 #define BOXPLOT_BOX               ' '
136 /* "│", inverted */
137 #define BOXPLOT_MEDIAN            (120 | A_ALTCHARSET)
138 
139 #include "oping.h"
140 
141 #ifndef _POSIX_SAVED_IDS
142 # define _POSIX_SAVED_IDS 0
143 #endif
144 
145 #ifndef IPTOS_MINCOST
146 # define IPTOS_MINCOST 0x02
147 #endif
148 
149 /* Remove GNU specific __attribute__ settings when using another compiler */
150 #if !__GNUC__
151 # define __attribute__(x) /**/
152 #endif
153 
154 typedef struct ping_context
155 {
156 	char host[NI_MAXHOST];
157 	char addr[NI_MAXHOST];
158 
159 	int index;
160 	int req_sent;
161 	int req_rcvd;
162 
163 	double latency_total;
164 
165 #ifndef HISTORY_SIZE_MAX
166 # define HISTORY_SIZE_MAX 900
167 #endif
168 	/* The last n RTTs in the order they were sent. */
169 	double history_by_time[HISTORY_SIZE_MAX];
170 
171 	/* Current number of entries in the history. This is a value between 0
172 	 * and HISTORY_SIZE_MAX. */
173 	size_t history_size;
174 
175 	/* Number "received" entries in the history, i.e. non-NAN entries. */
176 	size_t history_received;
177 
178 	/* Index of the next RTT to be written to history_by_time. This wraps
179 	 * around to 0 once the histroty has grown to HISTORY_SIZE_MAX. */
180 	size_t history_index;
181 
182 	/* The last history_size RTTs sorted by value. timed out packets (NAN
183 	 * entries) are sorted to the back. */
184 	double history_by_value[HISTORY_SIZE_MAX];
185 
186 	/* If set to true, history_by_value has to be re-calculated. */
187 	_Bool history_dirty;
188 
189 #if USE_NCURSES
190 	WINDOW *window;
191 #endif
192 } ping_context_t;
193 
194 static double  opt_interval   = 1.0;
195 static int     opt_addrfamily = PING_DEF_AF;
196 static char   *opt_srcaddr    = NULL;
197 static char   *opt_device     = NULL;
198 static char   *opt_filename   = NULL;
199 static int     opt_count      = -1;
200 static int     opt_send_ttl   = 64;
201 static uint8_t opt_send_qos   = 0;
202 #define OPING_DEFAULT_PERCENTILE 95.0
203 static double  opt_percentile = -1.0;
204 static double  opt_exit_status_threshold = 1.0;
205 #if USE_NCURSES
206 static int     opt_show_graph = 1;
207 static int     opt_utf8       = 0;
208 #endif
209 
210 static int host_num = 0;
211 
212 #if USE_NCURSES
213 static WINDOW *main_win = NULL;
214 #endif
215 
sigint_handler(int signal)216 static void sigint_handler (int signal) /* {{{ */
217 {
218 	/* Make compiler happy */
219 	signal = 0;
220 	/* Exit the loop */
221 	opt_count = 0;
222 } /* }}} void sigint_handler */
223 
context_create(void)224 static ping_context_t *context_create (void) /* {{{ */
225 {
226 	ping_context_t *ret;
227 
228 	if ((ret = malloc (sizeof (ping_context_t))) == NULL)
229 		return (NULL);
230 
231 	memset (ret, '\0', sizeof (ping_context_t));
232 
233 	ret->latency_total = 0.0;
234 
235 #if USE_NCURSES
236 	ret->window = NULL;
237 #endif
238 
239 	return (ret);
240 } /* }}} ping_context_t *context_create */
241 
context_destroy(ping_context_t * context)242 static void context_destroy (ping_context_t *context) /* {{{ */
243 {
244 	if (context == NULL)
245 		return;
246 
247 #if USE_NCURSES
248 	if (context->window != NULL)
249 	{
250 		delwin (context->window);
251 		context->window = NULL;
252 	}
253 #endif
254 
255 	free (context);
256 } /* }}} void context_destroy */
257 
compare_double(void const * arg0,void const * arg1)258 static int compare_double (void const *arg0, void const *arg1) /* {{{ */
259 {
260 	double dbl0 = *((double *) arg0);
261 	double dbl1 = *((double *) arg1);
262 
263 	if (isnan (dbl0))
264 	{
265 		if (isnan (dbl1))
266 			return 0;
267 		else
268 			return 1;
269 	}
270 	else if (isnan (dbl1))
271 		return -1;
272 	else if (dbl0 < dbl1)
273 		return -1;
274 	else if (dbl0 > dbl1)
275 		return 1;
276 	else
277 		return 0;
278 } /* }}} int compare_double */
279 
clean_history(ping_context_t * ctx)280 static void clean_history (ping_context_t *ctx) /* {{{ */
281 {
282 	size_t i;
283 
284 	if (!ctx->history_dirty)
285 		return;
286 
287 	/* Copy all values from by_time to by_value. */
288 	memcpy (ctx->history_by_value, ctx->history_by_time,
289 			sizeof (ctx->history_by_time));
290 
291 	/* Sort all RTTs. */
292 	qsort (ctx->history_by_value, ctx->history_size, sizeof
293 			(ctx->history_by_value[0]), compare_double);
294 
295 	/* Update the number of received RTTs. */
296 	ctx->history_received = 0;
297 	for (i = 0; i < ctx->history_size; i++)
298 		if (!isnan (ctx->history_by_value[i]))
299 			ctx->history_received++;
300 
301 	/* Mark as clean. */
302 	ctx->history_dirty = 0;
303 } /* }}} void clean_history */
304 
percentile_to_latency(ping_context_t * ctx,double percentile)305 static double percentile_to_latency (ping_context_t *ctx, /* {{{ */
306 		double percentile)
307 {
308 	size_t index;
309 
310 	clean_history (ctx);
311 
312 	/* Not a single packet was received successfully. */
313 	if (ctx->history_received == 0)
314 		return NAN;
315 
316 	if (percentile <= 0.0)
317 		index = 0;
318 	else if (percentile >= 100.0)
319 		index = ctx->history_received - 1;
320 	else
321 	{
322 		index = (size_t) ceil ((percentile / 100.0) * ((double) ctx->history_received));
323 		assert (index > 0);
324 		index--;
325 	}
326 
327 	return (ctx->history_by_value[index]);
328 } /* }}} double percentile_to_latency */
329 
330 #if USE_NCURSES
latency_to_ratio(ping_context_t * ctx,double latency)331 static double latency_to_ratio (ping_context_t *ctx, /* {{{ */
332 		double latency)
333 {
334 	size_t low;
335 	size_t high;
336 	size_t index;
337 
338 	clean_history (ctx);
339 
340 	/* Not a single packet was received successfully. */
341 	if (ctx->history_received == 0)
342 		return NAN;
343 
344 	low = 0;
345 	high = ctx->history_received - 1;
346 
347 	if (latency < ctx->history_by_value[low])
348 		return 0.0;
349 	else if (latency >= ctx->history_by_value[high])
350 		return 100.0;
351 
352 	/* Do a binary search for the latency. This will work even when the
353 	 * exact latency is not in the array. If the latency is in the array
354 	 * multiple times, "low" will be set to the index of the last
355 	 * occurrence. The value at index "high" will be larger than the
356 	 * searched for latency (assured by the above "if" block. */
357 	while ((high - low) > 1)
358 	{
359 		index = (high + low) / 2;
360 
361 		if (ctx->history_by_value[index] > latency)
362 			high = index;
363 		else
364 			low = index;
365 	}
366 
367 	assert (ctx->history_by_value[high] > latency);
368 	assert (ctx->history_by_value[low] <= latency);
369 
370 	if (ctx->history_by_value[low] == latency)
371 		index = low;
372 	else
373 		index = high;
374 
375 	return (((double) (index + 1)) / ((double) ctx->history_received));
376 } /* }}} double latency_to_ratio */
377 #endif
378 
context_get_packet_loss(const ping_context_t * ctx)379 static double context_get_packet_loss (const ping_context_t *ctx) /* {{{ */
380 {
381 	if (ctx == NULL)
382 		return (-1.0);
383 
384 	if (ctx->req_sent < 1)
385 		return (0.0);
386 
387 	return (100.0 * (ctx->req_sent - ctx->req_rcvd)
388 			/ ((double) ctx->req_sent));
389 } /* }}} double context_get_packet_loss */
390 
ping_initialize_contexts(pingobj_t * ping)391 static int ping_initialize_contexts (pingobj_t *ping) /* {{{ */
392 {
393 	pingobj_iter_t *iter;
394 	int index;
395 
396 	if (ping == NULL)
397 		return (EINVAL);
398 
399 	index = 0;
400 	for (iter = ping_iterator_get (ping);
401 			iter != NULL;
402 			iter = ping_iterator_next (iter))
403 	{
404 		ping_context_t *context;
405 		size_t buffer_size;
406 
407 		context = context_create ();
408 		context->index = index;
409 
410 		buffer_size = sizeof (context->host);
411 		ping_iterator_get_info (iter, PING_INFO_HOSTNAME, context->host, &buffer_size);
412 
413 		buffer_size = sizeof (context->addr);
414 		ping_iterator_get_info (iter, PING_INFO_ADDRESS, context->addr, &buffer_size);
415 
416 		ping_iterator_set_context (iter, (void *) context);
417 
418 		index++;
419 	}
420 
421 	return (0);
422 } /* }}} int ping_initialize_contexts */
423 
usage_exit(const char * name,int status)424 static void usage_exit (const char *name, int status) /* {{{ */
425 {
426 	fprintf (stderr, "Usage: %s [OPTIONS] "
427 				"-f filename | host [host [host ...]]\n"
428 
429 			"\nAvailable options:\n"
430 			"  -4|-6        force the use of IPv4 or IPv6\n"
431 			"  -c count     number of ICMP packets to send\n"
432 			"  -i interval  interval with which to send ICMP packets\n"
433 			"  -t ttl       time to live for each ICMP packet\n"
434 			"  -Q qos       Quality of Service (QoS) of outgoing packets\n"
435 			"               Use \"-Q help\" for a list of valid options.\n"
436 			"  -I srcaddr   source address\n"
437 			"  -D device    outgoing interface name\n"
438 			"  -f filename  filename to read hosts from\n"
439 #if USE_NCURSES
440 			"  -u / -U      force / disable UTF-8 output\n"
441 			"  -g graph     graph type to draw\n"
442 #endif
443 			"  -P percent   Report the n'th percentile of latency\n"
444 			"  -Z percent   Exit with non-zero exit status if more than this percentage of\n"
445 			"               probes timed out. (default: never)\n"
446 
447 			"\noping "PACKAGE_VERSION", http://verplant.org/liboping/\n"
448 			"by Florian octo Forster <octo@verplant.org>\n"
449 			"for contributions see `AUTHORS'\n",
450 			name);
451 	exit (status);
452 } /* }}} void usage_exit */
453 
454 __attribute__((noreturn))
usage_qos_exit(const char * arg,int status)455 static void usage_qos_exit (const char *arg, int status) /* {{{ */
456 {
457 	if (arg != 0)
458 		fprintf (stderr, "Invalid QoS argument: \"%s\"\n\n", arg);
459 
460 	fprintf (stderr, "Valid QoS arguments (option \"-Q\") are:\n"
461 			"\n"
462 			"  Differentiated Services (IPv4 and IPv6, RFC 2474)\n"
463 			"\n"
464 			"    be                     Best Effort (BE, default PHB).\n"
465 			"    ef                     Expedited Forwarding (EF) PHB group (RFC 3246).\n"
466 			"                           (low delay, low loss, low jitter)\n"
467 			"    va                     Voice Admit (VA) DSCP (RFC 5865).\n"
468 			"                           (capacity-admitted traffic)\n"
469 			"    af[1-4][1-3]           Assured Forwarding (AF) PHB group (RFC 2597).\n"
470 			"                           For example: \"af12\" (class 1, precedence 2)\n"
471 			"    cs[0-7]                Class Selector (CS) PHB group (RFC 2474).\n"
472 			"                           For example: \"cs1\" (priority traffic)\n"
473 			"\n"
474 			"  Type of Service (IPv4, RFC 1349, obsolete)\n"
475 			"\n"
476 			"    lowdelay     (%#04x)    minimize delay\n"
477 			"    throughput   (%#04x)    maximize throughput\n"
478 			"    reliability  (%#04x)    maximize reliability\n"
479 			"    mincost      (%#04x)    minimize monetary cost\n"
480 			"\n"
481 			"  Specify manually\n"
482 			"\n"
483 			"    0x00 - 0xff            Hexadecimal numeric specification.\n"
484 			"       0 -  255            Decimal numeric specification.\n"
485 			"\n",
486 			(unsigned int) IPTOS_LOWDELAY,
487 			(unsigned int) IPTOS_THROUGHPUT,
488 			(unsigned int) IPTOS_RELIABILITY,
489 			(unsigned int) IPTOS_MINCOST);
490 
491 	exit (status);
492 } /* }}} void usage_qos_exit */
493 
set_opt_send_qos(const char * opt)494 static int set_opt_send_qos (const char *opt) /* {{{ */
495 {
496 	if (opt == NULL)
497 		return (EINVAL);
498 
499 	if (strcasecmp ("help", opt) == 0)
500 		usage_qos_exit (/* arg = */ NULL, /* status = */ EXIT_SUCCESS);
501 	/* DiffServ (RFC 2474): */
502 	/* - Best effort (BE) */
503 	else if (strcasecmp ("be", opt) == 0)
504 		opt_send_qos = 0;
505 	/* - Expedited Forwarding (EF, RFC 3246) */
506 	else if (strcasecmp ("ef", opt) == 0)
507 		opt_send_qos = 0xB8; /* == 0x2E << 2 */
508 	/* - Voice Admit (VA, RFC 5865) */
509 	else if (strcasecmp ("va", opt) == 0)
510 		opt_send_qos = 0xB0; /* == 0x2D << 2 */
511 	/* - Assured Forwarding (AF, RFC 2597) */
512 	else if ((strncasecmp ("af", opt, strlen ("af")) == 0)
513 			&& (strlen (opt) == 4))
514 	{
515 		uint8_t dscp;
516 		uint8_t class = 0;
517 		uint8_t prec = 0;
518 
519 		/* There are four classes, AF1x, AF2x, AF3x, and AF4x. */
520 		if (opt[2] == '1')
521 			class = 1;
522 		else if (opt[2] == '2')
523 			class = 2;
524 		else if (opt[2] == '3')
525 			class = 3;
526 		else if (opt[2] == '4')
527 			class = 4;
528 		else
529 			usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_SUCCESS);
530 
531 		/* In each class, there are three precedences, AFx1, AFx2, and AFx3 */
532 		if (opt[3] == '1')
533 			prec = 1;
534 		else if (opt[3] == '2')
535 			prec = 2;
536 		else if (opt[3] == '3')
537 			prec = 3;
538 		else
539 			usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_SUCCESS);
540 
541 		dscp = (8 * class) + (2 * prec);
542 		/* The lower two bits are used for Explicit Congestion Notification (ECN) */
543 		opt_send_qos = dscp << 2;
544 	}
545 	/* - Class Selector (CS) */
546 	else if ((strncasecmp ("cs", opt, strlen ("cs")) == 0)
547 			&& (strlen (opt) == 3))
548 	{
549 		uint8_t class;
550 
551 		if ((opt[2] < '0') || (opt[2] > '7'))
552 			usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_FAILURE);
553 
554 		/* Not exactly legal by the C standard, but I don't know of any
555 		 * system not supporting this hack. */
556 		class = ((uint8_t) opt[2]) - ((uint8_t) '0');
557 		opt_send_qos = class << 5;
558 	}
559 	/* Type of Service (RFC 1349) */
560 	else if (strcasecmp ("lowdelay", opt) == 0)
561 		opt_send_qos = IPTOS_LOWDELAY;
562 	else if (strcasecmp ("throughput", opt) == 0)
563 		opt_send_qos = IPTOS_THROUGHPUT;
564 	else if (strcasecmp ("reliability", opt) == 0)
565 		opt_send_qos = IPTOS_RELIABILITY;
566 	else if (strcasecmp ("mincost", opt) == 0)
567 		opt_send_qos = IPTOS_MINCOST;
568 	/* Numeric value */
569 	else
570 	{
571 		unsigned long value;
572 		char *endptr;
573 
574 		errno = 0;
575 		endptr = NULL;
576 		value = strtoul (opt, &endptr, /* base = */ 0);
577 		if ((errno != 0) || (endptr == opt)
578 				|| (endptr == NULL) || (*endptr != 0)
579 				|| (value > 0xff))
580 			usage_qos_exit (/* arg = */ opt, /* status = */ EXIT_FAILURE);
581 
582 		opt_send_qos = (uint8_t) value;
583 	}
584 
585 	return (0);
586 } /* }}} int set_opt_send_qos */
587 
format_qos(uint8_t qos,char * buffer,size_t buffer_size)588 static char *format_qos (uint8_t qos, char *buffer, size_t buffer_size) /* {{{ */
589 {
590 	uint8_t dscp;
591 	uint8_t ecn;
592 	char *dscp_str;
593 	char *ecn_str;
594 
595 	dscp = qos >> 2;
596 	ecn = qos & 0x03;
597 
598 	switch (dscp)
599 	{
600 		case 0x00: dscp_str = "be";  break;
601 		case 0x2e: dscp_str = "ef";  break;
602 		case 0x2d: dscp_str = "va";  break;
603 		case 0x0a: dscp_str = "af11"; break;
604 		case 0x0c: dscp_str = "af12"; break;
605 		case 0x0e: dscp_str = "af13"; break;
606 		case 0x12: dscp_str = "af21"; break;
607 		case 0x14: dscp_str = "af22"; break;
608 		case 0x16: dscp_str = "af23"; break;
609 		case 0x1a: dscp_str = "af31"; break;
610 		case 0x1c: dscp_str = "af32"; break;
611 		case 0x1e: dscp_str = "af33"; break;
612 		case 0x22: dscp_str = "af41"; break;
613 		case 0x24: dscp_str = "af42"; break;
614 		case 0x26: dscp_str = "af43"; break;
615 		case 0x08: dscp_str = "cs1";  break;
616 		case 0x10: dscp_str = "cs2";  break;
617 		case 0x18: dscp_str = "cs3";  break;
618 		case 0x20: dscp_str = "cs4";  break;
619 		case 0x28: dscp_str = "cs5";  break;
620 		case 0x30: dscp_str = "cs6";  break;
621 		case 0x38: dscp_str = "cs7";  break;
622 		default:   dscp_str = NULL;
623 	}
624 
625 	switch (ecn)
626 	{
627 		case 0x01: ecn_str = ",ecn(1)"; break;
628 		case 0x02: ecn_str = ",ecn(0)"; break;
629 		case 0x03: ecn_str = ",ce"; break;
630 		default:   ecn_str = "";
631 	}
632 
633 	if (dscp_str == NULL)
634 		snprintf (buffer, buffer_size, "0x%02x%s", dscp, ecn_str);
635 	else
636 		snprintf (buffer, buffer_size, "%s%s", dscp_str, ecn_str);
637 	buffer[buffer_size - 1] = 0;
638 
639 	return (buffer);
640 } /* }}} char *format_qos */
641 
read_options(int argc,char ** argv)642 static int read_options (int argc, char **argv) /* {{{ */
643 {
644 	int optchar;
645 
646 	while (1)
647 	{
648 		optchar = getopt (argc, argv, "46c:hi:I:t:Q:f:D:Z:P:"
649 #if USE_NCURSES
650 				"uUg:"
651 #endif
652 				);
653 
654 		if (optchar == -1)
655 			break;
656 
657 		switch (optchar)
658 		{
659 			case '4':
660 			case '6':
661 				opt_addrfamily = (optchar == '4') ? AF_INET : AF_INET6;
662 				break;
663 
664 			case 'c':
665 				{
666 					int new_count;
667 					new_count = atoi (optarg);
668 					if (new_count > 0)
669 					{
670 						opt_count = new_count;
671 
672 						if ((opt_percentile < 0.0) && (opt_count < 20))
673 							opt_percentile = 100.0 * (opt_count - 1) / opt_count;
674 					}
675 					else
676 						fprintf(stderr, "Ignoring invalid count: %s\n",
677 								optarg);
678 				}
679 				break;
680 
681 			case 'f':
682 				{
683 					if (opt_filename != NULL)
684 						free (opt_filename);
685 					opt_filename = strdup (optarg);
686 				}
687 				break;
688 
689 			case 'i':
690 				{
691 					double new_interval;
692 					new_interval = atof (optarg);
693 					if (new_interval < 0.001)
694 						fprintf (stderr, "Ignoring invalid interval: %s\n",
695 								optarg);
696 					else
697 						opt_interval = new_interval;
698 				}
699 				break;
700 
701 			case 'I':
702 				{
703 					if (opt_srcaddr != NULL)
704 						free (opt_srcaddr);
705 					opt_srcaddr = strdup (optarg);
706 				}
707 				break;
708 
709 			case 'D':
710 				opt_device = optarg;
711 				break;
712 
713 			case 't':
714 			{
715 				int new_send_ttl;
716 				new_send_ttl = atoi (optarg);
717 				if ((new_send_ttl > 0) && (new_send_ttl < 256))
718 					opt_send_ttl = new_send_ttl;
719 				else
720 					fprintf (stderr, "Ignoring invalid TTL argument: %s\n",
721 							optarg);
722 				break;
723 			}
724 
725 			case 'Q':
726 				set_opt_send_qos (optarg);
727 				break;
728 
729 			case 'P':
730 				{
731 					double new_percentile;
732 					new_percentile = atof (optarg);
733 					if (isnan (new_percentile)
734 							|| (new_percentile < 0.1)
735 							|| (new_percentile > 100.0))
736 						fprintf (stderr, "Ignoring invalid percentile: %s\n",
737 								optarg);
738 					else
739 						opt_percentile = new_percentile;
740 				}
741 				break;
742 
743 #if USE_NCURSES
744 			case 'g':
745 				if (strcasecmp ("none", optarg) == 0)
746 					opt_show_graph = 0;
747 				else if (strcasecmp ("prettyping", optarg) == 0)
748 					opt_show_graph = 1;
749 				else if (strcasecmp ("histogram", optarg) == 0)
750 					opt_show_graph = 2;
751 				else if (strcasecmp ("boxplot", optarg) == 0)
752 					opt_show_graph = 3;
753 				else
754 					fprintf (stderr, "Unknown graph option: %s\n", optarg);
755 				break;
756 
757 			case 'u':
758 				opt_utf8 = 2;
759 				break;
760 			case 'U':
761 				opt_utf8 = 1;
762 				break;
763 #endif
764 
765 			case 'Z':
766 			{
767 				char *endptr = NULL;
768 				double tmp;
769 
770 				errno = 0;
771 				tmp = strtod (optarg, &endptr);
772 				if ((errno != 0) || (endptr == NULL) || (*endptr != 0) || (tmp < 0.0) || (tmp > 100.0))
773 				{
774 					fprintf (stderr, "Ignoring invalid -Z argument: %s\n", optarg);
775 					fprintf (stderr, "The \"-Z\" option requires a numeric argument between 0 and 100.\n");
776 				}
777 				else
778 					opt_exit_status_threshold = tmp / 100.0;
779 
780 				break;
781 			}
782 
783 			case 'h':
784 				usage_exit (argv[0], 0);
785 				break;
786 
787 			default:
788 				usage_exit (argv[0], 1);
789 		}
790 	}
791 
792 	if (opt_percentile <= 0.0)
793 		opt_percentile = OPING_DEFAULT_PERCENTILE;
794 
795 	return (optind);
796 } /* }}} read_options */
797 
time_normalize(struct timespec * ts)798 static void time_normalize (struct timespec *ts) /* {{{ */
799 {
800 	while (ts->tv_nsec < 0)
801 	{
802 		if (ts->tv_sec == 0)
803 		{
804 			ts->tv_nsec = 0;
805 			return;
806 		}
807 
808 		ts->tv_sec  -= 1;
809 		ts->tv_nsec += 1000000000;
810 	}
811 
812 	while (ts->tv_nsec >= 1000000000)
813 	{
814 		ts->tv_sec  += 1;
815 		ts->tv_nsec -= 1000000000;
816 	}
817 } /* }}} void time_normalize */
818 
time_calc(struct timespec * ts_dest,const struct timespec * ts_int,const struct timeval * tv_begin,const struct timeval * tv_end)819 static void time_calc (struct timespec *ts_dest, /* {{{ */
820 		const struct timespec *ts_int,
821 		const struct timeval  *tv_begin,
822 		const struct timeval  *tv_end)
823 {
824 	ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
825 	ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
826 	time_normalize (ts_dest);
827 
828 	/* Assure that `(begin + interval) > end'.
829 	 * This may seem overly complicated, but `tv_sec' is of type `time_t'
830 	 * which may be `unsigned. *sigh* */
831 	if ((tv_end->tv_sec > ts_dest->tv_sec)
832 			|| ((tv_end->tv_sec == ts_dest->tv_sec)
833 				&& ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
834 	{
835 		ts_dest->tv_sec  = 0;
836 		ts_dest->tv_nsec = 0;
837 		return;
838 	}
839 
840 	ts_dest->tv_sec = ts_dest->tv_sec - tv_end->tv_sec;
841 	ts_dest->tv_nsec = ts_dest->tv_nsec - (tv_end->tv_usec * 1000);
842 	time_normalize (ts_dest);
843 } /* }}} void time_calc */
844 
845 #if USE_NCURSES
has_utf8()846 static _Bool has_utf8() /* {{{ */
847 {
848 # if HAVE_NCURSESW_NCURSES_H
849 	if (!opt_utf8)
850 	{
851 		/* Automatically determine */
852 		if (strcasecmp ("UTF-8", nl_langinfo (CODESET)) == 0)
853 			opt_utf8 = 2;
854 		else
855 			opt_utf8 = 1;
856 	}
857 	return ((_Bool) (opt_utf8 - 1));
858 # else
859 	return (0);
860 # endif
861 } /* }}} _Bool has_utf8 */
862 
update_graph_boxplot(ping_context_t * ctx)863 static int update_graph_boxplot (ping_context_t *ctx) /* {{{ */
864 {
865 	uint32_t *counters;
866 	double *ratios;
867 	size_t i;
868 	size_t x_max;
869 	size_t x;
870 
871 	clean_history (ctx);
872 
873 	if (ctx->history_received == 0)
874 		return (ENOENT);
875 
876 	x_max = (size_t) getmaxx (ctx->window);
877 	if (x_max <= 8)
878 		return (EINVAL);
879 	x_max -= 4;
880 
881 	counters = calloc (x_max, sizeof (*counters));
882 	ratios = calloc (x_max, sizeof (*ratios));
883 
884 	/* Bucketize */
885 	for (i = 0; i < ctx->history_received; i++)
886 	{
887 		double latency = ctx->history_by_value[i] / 1000.0;
888 		size_t index = (size_t) (((double) x_max) * latency / opt_interval);
889 
890 		if (index >= x_max)
891 			index = x_max - 1;
892 
893 		counters[index]++;
894 	}
895 
896 	/* Sum and calc ratios */
897 	ratios[0] = ((double) counters[0]) / ((double) ctx->history_received);
898 	for (x = 1; x < x_max; x++)
899 	{
900 		counters[x] += counters[x - 1];
901 		ratios[x] = ((double) counters[x]) / ((double) ctx->history_received);
902 	}
903 
904 	for (x = 0; x < x_max; x++)
905 	{
906 		int symbol = ' ';
907 		_Bool reverse = 0;
908 
909 		if (x == 0)
910 		{
911 			if (ratios[x] >= 0.5)
912 			{
913 				symbol = BOXPLOT_MEDIAN;
914 				reverse = 1;
915 			}
916 			else if (ratios[x] > 0.25)
917 			{
918 				symbol = BOXPLOT_BOX;
919 				reverse = 1;
920 			}
921 			else if (ratios[x] > 0.025)
922 				symbol = BOXPLOT_WHISKER_BAR;
923 			else
924 				symbol = ' '; /* NOP */
925 		}
926 		else /* (x != 0) */
927 		{
928 			if ((ratios[x - 1] < 0.5) && (ratios[x] >= 0.5))
929 			{
930 				symbol = BOXPLOT_MEDIAN;
931 				reverse = 1;
932 			}
933 			else if (((ratios[x] >= 0.25) && (ratios[x] <= 0.75))
934 					|| ((ratios[x - 1] < 0.75) && (ratios[x] > 0.75)))
935 			{
936 				symbol = BOXPLOT_BOX;
937 				reverse = 1;
938 			}
939 			else if ((ratios[x] < 0.5) && (ratios[x] >= 0.025))
940 			{
941 				if (ratios[x - 1] < 0.025)
942 					symbol = BOXPLOT_WHISKER_LEFT_END;
943 				else
944 					symbol = BOXPLOT_WHISKER_BAR;
945 			}
946 			else if ((ratios[x] > .5) && (ratios[x] < 0.975))
947 			{
948 				symbol = BOXPLOT_WHISKER_BAR;
949 			}
950 			else if ((ratios[x] >= 0.975) && (ratios[x - 1] < 0.975))
951 				symbol = BOXPLOT_WHISKER_RIGHT_END;
952 		}
953 
954 		if (reverse)
955 			wattron (ctx->window, A_REVERSE);
956 		mvwaddch (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
957 		// mvwprintw (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
958 		if (reverse)
959 			wattroff (ctx->window, A_REVERSE);
960 	}
961 
962 	free (counters);
963 	free (ratios);
964 	return (0);
965 } /* }}} int update_graph_boxplot */
966 
update_graph_prettyping(ping_context_t * ctx,double latency,unsigned int sequence)967 static int update_graph_prettyping (ping_context_t *ctx, /* {{{ */
968 		double latency, unsigned int sequence)
969 {
970 	size_t x;
971 	size_t x_max;
972 	size_t history_offset;
973 
974 	x_max = (size_t) getmaxx (ctx->window);
975 	if (x_max <= 4)
976 		return (EINVAL);
977 	x_max -= 4;
978 
979 	/* Determine the first index in the history we need to draw
980 	 * the graph. */
981 	history_offset = 0;
982 	if (((size_t) x_max) < ctx->history_size) /* window is smaller than history */
983 	{
984 		if (ctx->history_index > x_max)
985 			history_offset = ctx->history_index - x_max;
986 		else /* wrap around */
987 			history_offset = ctx->history_index + ctx->history_size - x_max;
988 	}
989 	else /* window is larger than history */
990 	{
991 		if (ctx->history_index != ctx->history_size) /* no longer growing. */
992 			history_offset = ctx->history_index;
993 		else /* start-up */
994 			history_offset = 0;
995 	}
996 
997 	for (x = 0; x < x_max; x++)
998 	{
999 		size_t index;
1000 		double latency;
1001 
1002 		int color = OPING_RED;
1003 		char const *symbol = "!";
1004 		int symbolc = '!';
1005 
1006 		if (x >= ctx->history_size)
1007 		{
1008 			mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, ' ');
1009 			continue;
1010 		}
1011 
1012 		index = (history_offset + x) % ctx->history_size;
1013 		latency = ctx->history_by_time[index];
1014 
1015 		if (latency >= 0.0)
1016 		{
1017 			double ratio;
1018 
1019 			size_t symbols_num = hist_symbols_acs_num;
1020 			size_t colors_num = 1;
1021 
1022 			size_t index_symbols;
1023 			size_t index_colors;
1024 			size_t intensity;
1025 
1026 			/* latency is in milliseconds, opt_interval is in seconds. */
1027 			ratio = (latency * 0.001) / opt_interval;
1028 			if (ratio > 1) {
1029 				ratio = 1.0;
1030 			}
1031 
1032 			if (has_utf8 ())
1033 				symbols_num = hist_symbols_utf8_num;
1034 
1035 			if (has_colors () == TRUE)
1036 				colors_num = hist_colors_num;
1037 
1038 			intensity = (size_t) (ratio * ((double) (symbols_num * colors_num)));
1039 			if (intensity >= (symbols_num * colors_num))
1040 				intensity = (symbols_num * colors_num) - 1;
1041 
1042 			index_symbols = intensity % symbols_num;
1043 			assert (index_symbols < symbols_num);
1044 
1045 			index_colors = intensity / symbols_num;
1046 			assert (index_colors < colors_num);
1047 
1048 			if (has_utf8())
1049 			{
1050 				color = hist_colors_utf8[index_colors];
1051 				symbol = hist_symbols_utf8[index_symbols];
1052 			}
1053 			else
1054 			{
1055 				color = hist_colors_acs[index_colors];
1056 				symbolc = hist_symbols_acs[index_symbols] | A_ALTCHARSET;
1057 			}
1058 		}
1059 		else /* if (!(latency >= 0.0)) */
1060 			wattron (ctx->window, A_BOLD);
1061 
1062 		if (has_colors () == TRUE)
1063 			wattron (ctx->window, COLOR_PAIR(color));
1064 
1065 		if (has_utf8())
1066 			mvwprintw (ctx->window, /* y = */ 3, /* x = */ x + 2, symbol);
1067 		else
1068 			mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, symbolc);
1069 
1070 		if (has_colors () == TRUE)
1071 			wattroff (ctx->window, COLOR_PAIR(color));
1072 
1073 		/* Use negation here to handle NaN correctly. */
1074 		if (!(latency >= 0.0))
1075 			wattroff (ctx->window, A_BOLD);
1076 	} /* for (x) */
1077 
1078 	return (0);
1079 } /* }}} int update_graph_prettyping */
1080 
update_graph_histogram(ping_context_t * ctx)1081 static int update_graph_histogram (ping_context_t *ctx) /* {{{ */
1082 {
1083 	uint32_t *counters;
1084 	uint32_t *accumulated;
1085 	uint32_t max;
1086 	size_t i;
1087 	size_t x_max;
1088 	size_t x;
1089 
1090 	size_t symbols_num = hist_symbols_acs_num;
1091 
1092 	clean_history (ctx);
1093 
1094 	if (ctx->history_received == 0)
1095 		return (ENOENT);
1096 
1097 	if (has_utf8 ())
1098 		symbols_num = hist_symbols_utf8_num;
1099 
1100 	x_max = (size_t) getmaxx (ctx->window);
1101 	if (x_max <= 4)
1102 		return (EINVAL);
1103 	x_max -= 4;
1104 
1105 	counters = calloc (x_max, sizeof (*counters));
1106 	accumulated = calloc (x_max, sizeof (*accumulated));
1107 
1108 	/* Bucketize */
1109 	max = 0;
1110 	for (i = 0; i < ctx->history_received; i++)
1111 	{
1112 		double latency = ctx->history_by_value[i] / 1000.0;
1113 		size_t index = (size_t) (((double) x_max) * latency / opt_interval);
1114 
1115 		if (index >= x_max)
1116 			index = x_max - 1;
1117 
1118 		counters[index]++;
1119 		if (max < counters[index])
1120 			max = counters[index];
1121 	}
1122 
1123 	/* Sum */
1124 	accumulated[0] = counters[0];
1125 	for (x = 1; x < x_max; x++)
1126 		accumulated[x] = counters[x] + accumulated[x - 1];
1127 
1128 	/* Calculate ratios */
1129 	for (x = 0; x < x_max; x++)
1130 	{
1131 		double height = ((double) counters[x]) / ((double) max);
1132 		double ratio_this = ((double) accumulated[x]) / ((double) ctx->history_received);
1133 		double ratio_prev = 0.0;
1134 		size_t index;
1135 		int color = 0;
1136 
1137 		index = (size_t) (height * ((double) symbols_num));
1138 		if (index >= symbols_num)
1139 			index = symbols_num - 1;
1140 
1141 		if (x > 0)
1142 			ratio_prev = ((double) accumulated[x - 1]) / ((double) ctx->history_received);
1143 
1144 		if (has_colors () == TRUE)
1145 		{
1146 			if ((ratio_this <= threshold_green)
1147 					|| ((ratio_prev < threshold_green)
1148 						&& (ratio_this > threshold_green)))
1149 				color = OPING_GREEN;
1150 			else if ((ratio_this <= threshold_yellow)
1151 					|| ((ratio_prev < threshold_yellow)
1152 						&& (ratio_this > threshold_yellow)))
1153 				color = OPING_YELLOW;
1154 			else
1155 				color = OPING_RED;
1156 
1157 			wattron (ctx->window, COLOR_PAIR(color));
1158 		}
1159 
1160 		if (counters[x] == 0)
1161 			mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, ' ');
1162 		else if (has_utf8 ())
1163 			mvwprintw (ctx->window, /* y = */ 3, /* x = */ x + 2,
1164 					hist_symbols_utf8[index]);
1165 		else
1166 			mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2,
1167 					hist_symbols_acs[index] | A_ALTCHARSET);
1168 
1169 		if (has_colors () == TRUE)
1170 			wattroff (ctx->window, COLOR_PAIR(color));
1171 
1172 	}
1173 
1174 	free (accumulated);
1175 	return (0);
1176 } /* }}} int update_graph_histogram */
1177 
update_stats_from_context(ping_context_t * ctx,pingobj_iter_t * iter)1178 static int update_stats_from_context (ping_context_t *ctx, pingobj_iter_t *iter) /* {{{ */
1179 {
1180 	double latency = -1.0;
1181 	size_t buffer_len = sizeof (latency);
1182 
1183 	ping_iterator_get_info (iter, PING_INFO_LATENCY,
1184 			&latency, &buffer_len);
1185 
1186 	unsigned int sequence = 0;
1187 	buffer_len = sizeof (sequence);
1188 	ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
1189 			&sequence, &buffer_len);
1190 
1191 
1192 	if ((ctx == NULL) || (ctx->window == NULL))
1193 		return (EINVAL);
1194 
1195 	/* werase (ctx->window); */
1196 
1197 	box (ctx->window, 0, 0);
1198 	wattron (ctx->window, A_BOLD);
1199 	mvwprintw (ctx->window, /* y = */ 0, /* x = */ 5,
1200 			" %s ", ctx->host);
1201 	wattroff (ctx->window, A_BOLD);
1202 	wprintw (ctx->window, "ping statistics ");
1203 	mvwprintw (ctx->window, /* y = */ 1, /* x = */ 2,
1204 			"%i packets transmitted, %i received, %.2f%% packet "
1205 			"loss, time %.1fms",
1206 			ctx->req_sent, ctx->req_rcvd,
1207 			context_get_packet_loss (ctx),
1208 			ctx->latency_total);
1209 	if (ctx->req_rcvd != 0)
1210 	{
1211 		double min;
1212 		double median;
1213 		double max;
1214 		double percentile;
1215 
1216 		min = percentile_to_latency (ctx, 0.0);
1217 		median = percentile_to_latency (ctx, 50.0);
1218 		max = percentile_to_latency (ctx, 100.0);
1219 		percentile = percentile_to_latency (ctx, opt_percentile);
1220 
1221 		mvwprintw (ctx->window, /* y = */ 2, /* x = */ 2,
1222 				"RTT[ms]: min = %.0f, median = %.0f, p(%.0f) = %.0f, max = %.0f  ",
1223 				min, median, opt_percentile, percentile, max);
1224 	}
1225 
1226 	if (opt_show_graph == 1)
1227 		update_graph_prettyping (ctx, latency, sequence);
1228 	else if (opt_show_graph == 2)
1229 		update_graph_histogram (ctx);
1230 	else if (opt_show_graph == 3)
1231 		update_graph_boxplot (ctx);
1232 
1233 	wrefresh (ctx->window);
1234 
1235 	return (0);
1236 } /* }}} int update_stats_from_context */
1237 
on_resize(pingobj_t * ping)1238 static int on_resize (pingobj_t *ping) /* {{{ */
1239 {
1240 	pingobj_iter_t *iter;
1241 	int width = 0;
1242 	int height = 0;
1243 	int main_win_height;
1244 	int box_height = (opt_show_graph == 0) ? 4 : 5;
1245 
1246 	getmaxyx (stdscr, height, width);
1247 	if ((height < 1) || (width < 1))
1248 		return (EINVAL);
1249 
1250 	main_win_height = height - (box_height * host_num);
1251 	wresize (main_win, main_win_height, /* width = */ width);
1252 	/* Allow scrolling */
1253 	scrollok (main_win, TRUE);
1254 	/* wsetscrreg (main_win, 0, main_win_height - 1); */
1255 	/* Allow hardware accelerated scrolling. */
1256 	idlok (main_win, TRUE);
1257 	wrefresh (main_win);
1258 
1259 	for (iter = ping_iterator_get (ping);
1260 			iter != NULL;
1261 			iter = ping_iterator_next (iter))
1262 	{
1263 		ping_context_t *context;
1264 
1265 		context = ping_iterator_get_context (iter);
1266 		if (context == NULL)
1267 			continue;
1268 
1269 		if (context->window != NULL)
1270 		{
1271 			delwin (context->window);
1272 			context->window = NULL;
1273 		}
1274 		context->window = newwin (/* height = */ box_height,
1275 				/* width = */ width,
1276 				/* y = */ main_win_height + (box_height * context->index),
1277 				/* x = */ 0);
1278 	}
1279 
1280 	return (0);
1281 } /* }}} */
1282 
check_resize(pingobj_t * ping)1283 static int check_resize (pingobj_t *ping) /* {{{ */
1284 {
1285 	int need_resize = 0;
1286 
1287 	while (42)
1288 	{
1289 		int key = wgetch (stdscr);
1290 		if (key == ERR)
1291 			break;
1292 		else if (key == KEY_RESIZE)
1293 			need_resize = 1;
1294 		else if (key == 'g')
1295 		{
1296 			if (opt_show_graph == 3)
1297 				opt_show_graph = 1;
1298 			else if (opt_show_graph > 0)
1299 				opt_show_graph++;
1300 		}
1301 	}
1302 
1303 	if (need_resize)
1304 		return (on_resize (ping));
1305 	else
1306 		return (0);
1307 } /* }}} int check_resize */
1308 
pre_loop_hook(pingobj_t * ping)1309 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
1310 {
1311 	pingobj_iter_t *iter;
1312 	int width = 0;
1313 	int height = 0;
1314 	int main_win_height;
1315 	int box_height = (opt_show_graph == 0) ? 4 : 5;
1316 
1317 	initscr ();
1318 	cbreak ();
1319 	noecho ();
1320 	nodelay (stdscr, TRUE);
1321 
1322 	getmaxyx (stdscr, height, width);
1323 	if ((height < 1) || (width < 1))
1324 		return (EINVAL);
1325 
1326 	if (has_colors () == TRUE)
1327 	{
1328 		start_color ();
1329 		init_pair (OPING_GREEN,  COLOR_GREEN,  /* default = */ 0);
1330 		init_pair (OPING_YELLOW, COLOR_YELLOW, /* default = */ 0);
1331 		init_pair (OPING_RED,    COLOR_RED,    /* default = */ 0);
1332 		init_pair (OPING_GREEN_HIST,  COLOR_GREEN,  COLOR_BLACK);
1333 		init_pair (OPING_YELLOW_HIST, COLOR_YELLOW, COLOR_GREEN);
1334 		init_pair (OPING_RED_HIST,    COLOR_RED,    COLOR_YELLOW);
1335 	}
1336 
1337 	main_win_height = height - (box_height * host_num);
1338 	main_win = newwin (/* height = */ main_win_height,
1339 			/* width = */ width,
1340 			/* y = */ 0, /* x = */ 0);
1341 	/* Allow scrolling */
1342 	scrollok (main_win, TRUE);
1343 	/* wsetscrreg (main_win, 0, main_win_height - 1); */
1344 	/* Allow hardware accelerated scrolling. */
1345 	idlok (main_win, TRUE);
1346 	wmove (main_win, /* y = */ main_win_height - 1, /* x = */ 0);
1347 	wrefresh (main_win);
1348 
1349 	for (iter = ping_iterator_get (ping);
1350 			iter != NULL;
1351 			iter = ping_iterator_next (iter))
1352 	{
1353 		ping_context_t *context;
1354 
1355 		context = ping_iterator_get_context (iter);
1356 		if (context == NULL)
1357 			continue;
1358 
1359 		if (context->window != NULL)
1360 		{
1361 			delwin (context->window);
1362 			context->window = NULL;
1363 		}
1364 		context->window = newwin (/* height = */ box_height,
1365 				/* width = */ width,
1366 				/* y = */ main_win_height + (box_height * context->index),
1367 				/* x = */ 0);
1368 	}
1369 
1370 
1371 	/* Don't know what good this does exactly, but without this code
1372 	 * "check_resize" will be called right after startup and *somehow*
1373 	 * this leads to display errors. If we purge all initial characters
1374 	 * here, the problem goes away. "wgetch" is non-blocking due to
1375 	 * "nodelay" (see above). */
1376 	while (wgetch (stdscr) != ERR)
1377 	{
1378 		/* eat up characters */;
1379 	}
1380 
1381 	return (0);
1382 } /* }}} int pre_loop_hook */
1383 
pre_sleep_hook(pingobj_t * ping)1384 static int pre_sleep_hook (pingobj_t *ping) /* {{{ */
1385 {
1386 	return (check_resize (ping));
1387 } /* }}} int pre_sleep_hook */
1388 
post_sleep_hook(pingobj_t * ping)1389 static int post_sleep_hook (pingobj_t *ping) /* {{{ */
1390 {
1391 	return (check_resize (ping));
1392 } /* }}} int pre_sleep_hook */
1393 #else /* if !USE_NCURSES */
pre_loop_hook(pingobj_t * ping)1394 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
1395 {
1396 	pingobj_iter_t *iter;
1397 
1398 	for (iter = ping_iterator_get (ping);
1399 			iter != NULL;
1400 			iter = ping_iterator_next (iter))
1401 	{
1402 		ping_context_t *ctx;
1403 		size_t buffer_size;
1404 
1405 		ctx = ping_iterator_get_context (iter);
1406 		if (ctx == NULL)
1407 			continue;
1408 
1409 		buffer_size = 0;
1410 		ping_iterator_get_info (iter, PING_INFO_DATA, NULL, &buffer_size);
1411 
1412 		printf ("PING %s (%s) %zu bytes of data.\n",
1413 				ctx->host, ctx->addr, buffer_size);
1414 	}
1415 
1416 	return (0);
1417 } /* }}} int pre_loop_hook */
1418 
pre_sleep_hook(pingobj_t * ping)1419 static int pre_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
1420 {
1421 	fflush (stdout);
1422 
1423 	return (0);
1424 } /* }}} int pre_sleep_hook */
1425 
post_sleep_hook(pingobj_t * ping)1426 static int post_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
1427 {
1428 	return (0);
1429 } /* }}} int post_sleep_hook */
1430 #endif
1431 
update_context(ping_context_t * ctx,double latency)1432 static void update_context (ping_context_t *ctx, double latency) /* {{{ */
1433 {
1434 	ctx->req_sent++;
1435 
1436 	if (latency > 0.0)
1437 	{
1438 		ctx->req_rcvd++;
1439 		ctx->latency_total += latency;
1440 	}
1441 	else
1442 	{
1443 		latency = NAN;
1444 	}
1445 
1446 	ctx->history_by_time[ctx->history_index] = latency;
1447 
1448 	ctx->history_dirty = 1;
1449 
1450 	/* Update index and size. */
1451 	ctx->history_index = (ctx->history_index + 1) % HISTORY_SIZE_MAX;
1452 	if (ctx->history_size < HISTORY_SIZE_MAX)
1453 		ctx->history_size++;
1454 } /* }}} void update_context */
1455 
update_host_hook(pingobj_iter_t * iter,int index)1456 static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
1457 		__attribute__((unused)) int index)
1458 {
1459 	double          latency;
1460 	unsigned int    sequence;
1461 	int             recv_ttl;
1462 	uint8_t         recv_qos;
1463 	char            recv_qos_str[16];
1464 	size_t          buffer_len;
1465 	size_t          data_len;
1466 	ping_context_t *context;
1467 
1468 	latency = -1.0;
1469 	buffer_len = sizeof (latency);
1470 	ping_iterator_get_info (iter, PING_INFO_LATENCY,
1471 			&latency, &buffer_len);
1472 
1473 	sequence = 0;
1474 	buffer_len = sizeof (sequence);
1475 	ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
1476 			&sequence, &buffer_len);
1477 
1478 	recv_ttl = -1;
1479 	buffer_len = sizeof (recv_ttl);
1480 	ping_iterator_get_info (iter, PING_INFO_RECV_TTL,
1481 			&recv_ttl, &buffer_len);
1482 
1483 	recv_qos = 0;
1484 	buffer_len = sizeof (recv_qos);
1485 	ping_iterator_get_info (iter, PING_INFO_RECV_QOS,
1486 			&recv_qos, &buffer_len);
1487 
1488 	data_len = 0;
1489 	ping_iterator_get_info (iter, PING_INFO_DATA,
1490 			NULL, &data_len);
1491 
1492 	context = (ping_context_t *) ping_iterator_get_context (iter);
1493 
1494 #if USE_NCURSES
1495 # define HOST_PRINTF(...) wprintw(main_win, __VA_ARGS__)
1496 #else
1497 # define HOST_PRINTF(...) printf(__VA_ARGS__)
1498 #endif
1499 
1500 	update_context (context, latency);
1501 
1502 	if (latency > 0.0)
1503 	{
1504 #if USE_NCURSES
1505 		if (has_colors () == TRUE)
1506 		{
1507 			double ratio;
1508 			int color = OPING_GREEN;
1509 
1510 			ratio = latency_to_ratio (context, latency);
1511 			if (ratio < threshold_green)
1512 				color = OPING_GREEN;
1513 			else if (ratio < threshold_yellow)
1514 				color = OPING_YELLOW;
1515 			else
1516 				color = OPING_RED;
1517 
1518 			HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i ",
1519 					data_len, context->host, context->addr,
1520 					sequence, recv_ttl,
1521 					format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1522 			if ((recv_qos != 0) || (opt_send_qos != 0))
1523 			{
1524 				HOST_PRINTF ("qos=%s ",
1525 						format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1526 			}
1527 			HOST_PRINTF ("time=");
1528 			wattron (main_win, COLOR_PAIR(color));
1529 			HOST_PRINTF ("%.2f", latency);
1530 			wattroff (main_win, COLOR_PAIR(color));
1531 			HOST_PRINTF (" ms\n");
1532 		}
1533 		else
1534 		{
1535 #endif
1536 		HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i ",
1537 				data_len,
1538 				context->host, context->addr,
1539 				sequence, recv_ttl);
1540 		if ((recv_qos != 0) || (opt_send_qos != 0))
1541 		{
1542 			HOST_PRINTF ("qos=%s ",
1543 					format_qos (recv_qos, recv_qos_str, sizeof (recv_qos_str)));
1544 		}
1545 		HOST_PRINTF ("time=%.2f ms\n", latency);
1546 #if USE_NCURSES
1547 		}
1548 #endif
1549 	}
1550 	else /* if (!(latency > 0.0)) */
1551 	{
1552 #if USE_NCURSES
1553 		if (has_colors () == TRUE)
1554 		{
1555 			HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u ",
1556 					context->host, context->addr,
1557 					sequence);
1558 			wattron (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
1559 			HOST_PRINTF ("timeout");
1560 			wattroff (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
1561 			HOST_PRINTF ("\n");
1562 		}
1563 		else
1564 		{
1565 #endif
1566 		HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u timeout\n",
1567 				context->host, context->addr,
1568 				sequence);
1569 #if USE_NCURSES
1570 		}
1571 #endif
1572 	}
1573 
1574 #if USE_NCURSES
1575 	update_stats_from_context (context, iter);
1576 	wrefresh (main_win);
1577 #endif
1578 } /* }}} void update_host_hook */
1579 
1580 /* Prints statistics for each host, cleans up the contexts and returns the
1581  * number of hosts which failed to return more than the fraction
1582  * opt_exit_status_threshold of pings. */
post_loop_hook(pingobj_t * ping)1583 static int post_loop_hook (pingobj_t *ping) /* {{{ */
1584 {
1585 	pingobj_iter_t *iter;
1586 	int failure_count = 0;
1587 
1588 #if USE_NCURSES
1589 	endwin ();
1590 #endif
1591 
1592 	for (iter = ping_iterator_get (ping);
1593 			iter != NULL;
1594 			iter = ping_iterator_next (iter))
1595 	{
1596 		ping_context_t *context;
1597 
1598 		context = ping_iterator_get_context (iter);
1599 
1600 		printf ("\n--- %s ping statistics ---\n"
1601 				"%i packets transmitted, %i received, %.2f%% packet loss, time %.1fms\n",
1602 				context->host, context->req_sent, context->req_rcvd,
1603 				context_get_packet_loss (context),
1604 				context->latency_total);
1605 
1606 		{
1607 			double pct_failed = 1.0 - (((double) context->req_rcvd)
1608 					/ ((double) context->req_sent));
1609 			if (pct_failed > opt_exit_status_threshold)
1610 				failure_count++;
1611 		}
1612 
1613 		if (context->req_rcvd != 0)
1614 		{
1615 			double min;
1616 			double median;
1617 			double max;
1618 			double percentile;
1619 
1620 			min = percentile_to_latency (context, 0.0);
1621 			median = percentile_to_latency (context, 50.0);
1622 			max = percentile_to_latency (context, 100.0);
1623 			percentile = percentile_to_latency (context, opt_percentile);
1624 
1625 			printf ("RTT[ms]: min = %.0f, median = %.0f, p(%.0f) = %.0f, max = %.0f\n",
1626 					min, median, opt_percentile, percentile, max);
1627 		}
1628 
1629 		ping_iterator_set_context (iter, NULL);
1630 		context_destroy (context);
1631 	}
1632 
1633 	return (failure_count);
1634 } /* }}} int post_loop_hook */
1635 
main(int argc,char ** argv)1636 int main (int argc, char **argv) /* {{{ */
1637 {
1638 	pingobj_t      *ping;
1639 	pingobj_iter_t *iter;
1640 
1641 	struct sigaction sigint_action;
1642 
1643 	struct timeval  tv_begin;
1644 	struct timeval  tv_end;
1645 	struct timespec ts_wait;
1646 	struct timespec ts_int;
1647 
1648 	int optind;
1649 	int i;
1650 	int status;
1651 #if _POSIX_SAVED_IDS
1652 	uid_t saved_set_uid;
1653 
1654 	/* Save the old effective user id */
1655 	saved_set_uid = geteuid ();
1656 	/* Set the effective user ID to the real user ID without changing the
1657 	 * saved set-user ID */
1658 	status = seteuid (getuid ());
1659 	if (status != 0)
1660 	{
1661 		fprintf (stderr, "Temporarily dropping privileges "
1662 				"failed: %s\n", strerror (errno));
1663 		exit (EXIT_FAILURE);
1664 	}
1665 #endif
1666 
1667         setlocale(LC_ALL, "");
1668 	optind = read_options (argc, argv);
1669 
1670 #if !_POSIX_SAVED_IDS
1671 	/* Cannot temporarily drop privileges -> reject every file but "-". */
1672 	if ((opt_filename != NULL)
1673 			&& (strcmp ("-", opt_filename) != 0)
1674 			&& (getuid () != geteuid ()))
1675 	{
1676 		fprintf (stderr, "Your real and effective user IDs don't "
1677 				"match. Reading from a file (option '-f')\n"
1678 				"is therefore too risky. You can still read "
1679 				"from STDIN using '-f -' if you like.\n"
1680 				"Sorry.\n");
1681 		exit (EXIT_FAILURE);
1682 	}
1683 #endif
1684 
1685 	if ((optind >= argc) && (opt_filename == NULL)) {
1686 		usage_exit (argv[0], 1);
1687 	}
1688 
1689 	if ((ping = ping_construct ()) == NULL)
1690 	{
1691 		fprintf (stderr, "ping_construct failed\n");
1692 		return (1);
1693 	}
1694 
1695 	if (ping_setopt (ping, PING_OPT_TTL, &opt_send_ttl) != 0)
1696 	{
1697 		fprintf (stderr, "Setting TTL to %i failed: %s\n",
1698 				opt_send_ttl, ping_get_error (ping));
1699 	}
1700 
1701 	if (ping_setopt (ping, PING_OPT_QOS, &opt_send_qos) != 0)
1702 	{
1703 		fprintf (stderr, "Setting TOS to %i failed: %s\n",
1704 				opt_send_qos, ping_get_error (ping));
1705 	}
1706 
1707 	{
1708 		double temp_sec;
1709 		double temp_nsec;
1710 
1711 		temp_nsec = modf (opt_interval, &temp_sec);
1712 		ts_int.tv_sec  = (time_t) temp_sec;
1713 		ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
1714 
1715 		/* printf ("ts_int = %i.%09li\n", (int) ts_int.tv_sec, ts_int.tv_nsec); */
1716 	}
1717 
1718 	if (opt_addrfamily != PING_DEF_AF)
1719 		ping_setopt (ping, PING_OPT_AF, (void *) &opt_addrfamily);
1720 
1721 	if (opt_srcaddr != NULL)
1722 	{
1723 		if (ping_setopt (ping, PING_OPT_SOURCE, (void *) opt_srcaddr) != 0)
1724 		{
1725 			fprintf (stderr, "Setting source address failed: %s\n",
1726 					ping_get_error (ping));
1727 		}
1728 	}
1729 
1730 	if (opt_device != NULL)
1731 	{
1732 		if (ping_setopt (ping, PING_OPT_DEVICE, (void *) opt_device) != 0)
1733 		{
1734 			fprintf (stderr, "Setting device failed: %s\n",
1735 					ping_get_error (ping));
1736 		}
1737 	}
1738 
1739 	if (opt_filename != NULL)
1740 	{
1741 		FILE *infile;
1742 		char line[256];
1743 		char host[256];
1744 
1745 		if (strcmp (opt_filename, "-") == 0)
1746 			/* Open STDIN */
1747 			infile = fdopen(0, "r");
1748 		else
1749 			infile = fopen(opt_filename, "r");
1750 
1751 		if (infile == NULL)
1752 		{
1753 			fprintf (stderr, "Opening %s failed: %s\n",
1754 					(strcmp (opt_filename, "-") == 0)
1755 					? "STDIN" : opt_filename,
1756 					strerror(errno));
1757 			return (1);
1758 		}
1759 
1760 #if _POSIX_SAVED_IDS
1761 		/* Regain privileges */
1762 		status = seteuid (saved_set_uid);
1763 		if (status != 0)
1764 		{
1765 			fprintf (stderr, "Temporarily re-gaining privileges "
1766 					"failed: %s\n", strerror (errno));
1767 			exit (EXIT_FAILURE);
1768 		}
1769 #endif
1770 
1771 		while (fgets(line, sizeof(line), infile))
1772 		{
1773 			/* Strip whitespace */
1774 			if (sscanf(line, "%s", host) != 1)
1775 				continue;
1776 
1777 			if ((host[0] == 0) || (host[0] == '#'))
1778 				continue;
1779 
1780 			if (ping_host_add(ping, host) < 0)
1781 			{
1782 				const char *errmsg = ping_get_error (ping);
1783 
1784 				fprintf (stderr, "Adding host `%s' failed: %s\n", host, errmsg);
1785 				continue;
1786 			}
1787 			else
1788 			{
1789 				host_num++;
1790 			}
1791 		}
1792 
1793 #if _POSIX_SAVED_IDS
1794 		/* Drop privileges */
1795 		status = seteuid (getuid ());
1796 		if (status != 0)
1797 		{
1798 			fprintf (stderr, "Temporarily dropping privileges "
1799 					"failed: %s\n", strerror (errno));
1800 			exit (EXIT_FAILURE);
1801 		}
1802 #endif
1803 
1804 		fclose(infile);
1805 	}
1806 
1807 #if _POSIX_SAVED_IDS
1808 	/* Regain privileges */
1809 	status = seteuid (saved_set_uid);
1810 	if (status != 0)
1811 	{
1812 		fprintf (stderr, "Temporarily re-gaining privileges "
1813 				"failed: %s\n", strerror (errno));
1814 		exit (EXIT_FAILURE);
1815 	}
1816 #endif
1817 
1818 	for (i = optind; i < argc; i++)
1819 	{
1820 		if (ping_host_add (ping, argv[i]) < 0)
1821 		{
1822 			const char *errmsg = ping_get_error (ping);
1823 
1824 			fprintf (stderr, "Adding host `%s' failed: %s\n", argv[i], errmsg);
1825 			continue;
1826 		}
1827 		else
1828 		{
1829 			host_num++;
1830 		}
1831 	}
1832 
1833 	/* Permanently drop root privileges if we're setuid-root. */
1834 	status = setuid (getuid ());
1835 	if (status != 0)
1836 	{
1837 		fprintf (stderr, "Dropping privileges failed: %s\n",
1838 				strerror (errno));
1839 		exit (EXIT_FAILURE);
1840 	}
1841 
1842 	if (host_num == 0)
1843 		exit (EXIT_FAILURE);
1844 
1845 #if _POSIX_SAVED_IDS
1846 	saved_set_uid = (uid_t) -1;
1847 #endif
1848 
1849 	ping_initialize_contexts (ping);
1850 
1851 	if (i == 0)
1852 		return (1);
1853 
1854 	memset (&sigint_action, '\0', sizeof (sigint_action));
1855 	sigint_action.sa_handler = sigint_handler;
1856 	if (sigaction (SIGINT, &sigint_action, NULL) < 0)
1857 	{
1858 		perror ("sigaction");
1859 		return (1);
1860 	}
1861 
1862 	pre_loop_hook (ping);
1863 
1864 	while (opt_count != 0)
1865 	{
1866 		int index;
1867 		int status;
1868 
1869 		if (gettimeofday (&tv_begin, NULL) < 0)
1870 		{
1871 			perror ("gettimeofday");
1872 			return (1);
1873 		}
1874 
1875 		status = ping_send (ping);
1876 		if (status == -EINTR)
1877 		{
1878 			continue;
1879 		}
1880 		else if (status < 0)
1881 		{
1882 			fprintf (stderr, "ping_send failed: %s\n",
1883 					ping_get_error (ping));
1884 			return (1);
1885 		}
1886 
1887 		index = 0;
1888 		for (iter = ping_iterator_get (ping);
1889 				iter != NULL;
1890 				iter = ping_iterator_next (iter))
1891 		{
1892 			update_host_hook (iter, index);
1893 			index++;
1894 		}
1895 
1896 		pre_sleep_hook (ping);
1897 
1898 		/* Don't sleep in the last iteration */
1899 		if (opt_count == 1)
1900 			break;
1901 
1902 		if (gettimeofday (&tv_end, NULL) < 0)
1903 		{
1904 			perror ("gettimeofday");
1905 			return (1);
1906 		}
1907 
1908 		time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
1909 
1910 		/* printf ("Sleeping for %i.%09li seconds\n", (int) ts_wait.tv_sec, ts_wait.tv_nsec); */
1911 		while ((status = nanosleep (&ts_wait, &ts_wait)) != 0)
1912 		{
1913 			if (errno == EINTR)
1914 			{
1915 				continue;
1916 			}
1917 			else
1918 			{
1919 				perror ("nanosleep");
1920 				break;
1921 			}
1922 		}
1923 
1924 		post_sleep_hook (ping);
1925 
1926 		if (opt_count > 0)
1927 			opt_count--;
1928 	} /* while (opt_count != 0) */
1929 
1930 	/* Returns the number of failed hosts according to -Z. */
1931 	status = post_loop_hook (ping);
1932 
1933 	ping_destroy (ping);
1934 
1935 	if (status == 0)
1936 		exit (EXIT_SUCCESS);
1937 	else
1938 	{
1939 		if (status > 255)
1940 			status = 255;
1941 		exit (status);
1942 	}
1943 } /* }}} int main */
1944 
1945 /* vim: set fdm=marker : */
1946