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