2 * Object oriented C module to send ICMP and ICMPv6 `echo's.
3 * Copyright (C) 2006-2010 Florian octo Forster <octo at verplant.org>
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
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.
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
29 # include <inttypes.h>
33 # error "You don't have the standard C99 header files installed"
34 #endif /* STDC_HEADERS */
44 #if TIME_WITH_SYS_TIME
45 # include <sys/time.h>
49 # include <sys/time.h>
56 # include <netdb.h> /* NI_MAXHOST */
64 #include <sys/types.h>
68 # define NCURSES_OPAQUE 1
71 # define OPING_GREEN 1
72 # define OPING_YELLOW 2
78 #ifndef _POSIX_SAVED_IDS
79 # define _POSIX_SAVED_IDS 0
82 typedef struct ping_context
84 char host[NI_MAXHOST];
85 char addr[NI_MAXHOST];
94 double latency_total_square;
101 static double opt_interval = 1.0;
102 static int opt_addrfamily = PING_DEF_AF;
103 static char *opt_srcaddr = NULL;
104 static char *opt_device = NULL;
105 static char *opt_filename = NULL;
106 static int opt_count = -1;
107 static int opt_send_ttl = 64;
108 static unsigned opt_send_tos = 0;
110 static int host_num = 0;
113 static WINDOW *main_win = NULL;
116 static void sigint_handler (int signal) /* {{{ */
118 /* Make compiler happy */
122 } /* }}} void sigint_handler */
124 static ping_context_t *context_create (void) /* {{{ */
128 if ((ret = malloc (sizeof (ping_context_t))) == NULL)
131 memset (ret, '\0', sizeof (ping_context_t));
133 ret->latency_min = -1.0;
134 ret->latency_max = -1.0;
135 ret->latency_total = 0.0;
136 ret->latency_total_square = 0.0;
143 } /* }}} ping_context_t *context_create */
145 static void context_destroy (ping_context_t *context) /* {{{ */
151 if (context->window != NULL)
153 delwin (context->window);
154 context->window = NULL;
159 } /* }}} void context_destroy */
161 static double context_get_average (ping_context_t *ctx) /* {{{ */
168 if (ctx->req_rcvd < 1)
171 num_total = (double) ctx->req_rcvd;
172 return (ctx->latency_total / num_total);
173 } /* }}} double context_get_average */
175 static double context_get_stddev (ping_context_t *ctx) /* {{{ */
182 if (ctx->req_rcvd < 1)
184 else if (ctx->req_rcvd < 2)
187 num_total = (double) ctx->req_rcvd;
188 return (sqrt (((num_total * ctx->latency_total_square)
189 - (ctx->latency_total * ctx->latency_total))
190 / (num_total * (num_total - 1.0))));
191 } /* }}} double context_get_stddev */
193 static double context_get_packet_loss (const ping_context_t *ctx) /* {{{ */
198 if (ctx->req_sent < 1)
201 return (100.0 * (ctx->req_sent - ctx->req_rcvd)
202 / ((double) ctx->req_sent));
203 } /* }}} double context_get_packet_loss */
205 static int ping_initialize_contexts (pingobj_t *ping) /* {{{ */
207 pingobj_iter_t *iter;
214 for (iter = ping_iterator_get (ping);
216 iter = ping_iterator_next (iter))
218 ping_context_t *context;
221 context = context_create ();
222 context->index = index;
224 buffer_size = sizeof (context->host);
225 ping_iterator_get_info (iter, PING_INFO_HOSTNAME, context->host, &buffer_size);
227 buffer_size = sizeof (context->addr);
228 ping_iterator_get_info (iter, PING_INFO_ADDRESS, context->addr, &buffer_size);
230 ping_iterator_set_context (iter, (void *) context);
236 } /* }}} int ping_initialize_contexts */
238 static void usage_exit (const char *name, int status) /* {{{ */
242 name_length = (int) strlen (name);
244 fprintf (stderr, "Usage: %s [OPTIONS] "
245 "-f filename | host [host [host ...]]\n"
247 "\nAvailable options:\n"
248 " -4|-6 force the use of IPv4 or IPv6\n"
249 " -c count number of ICMP packets to send\n"
250 " -i interval interval with which to send ICMP packets\n"
251 " -t ttl time to live for each ICMP packet\n"
252 " -z tos Type-of-service/class-of-service for each ICMP packet\n"
253 " -I srcaddr source address\n"
254 " -D device outgoing interface name\n"
255 " -f filename filename to read hosts from\n"
257 "\noping "PACKAGE_VERSION", http://verplant.org/liboping/\n"
258 "by Florian octo Forster <octo@verplant.org>\n"
259 "for contributions see `AUTHORS'\n",
262 } /* }}} void usage_exit */
264 static int read_options (int argc, char **argv) /* {{{ */
270 optchar = getopt (argc, argv, "46c:hi:I:t:z:f:D:");
279 opt_addrfamily = (optchar == '4') ? AF_INET : AF_INET6;
285 new_count = atoi (optarg);
287 opt_count = new_count;
289 fprintf(stderr, "Ignoring invalid count: %s\n",
296 if (opt_filename != NULL)
298 opt_filename = strdup (optarg);
305 new_interval = atof (optarg);
306 if (new_interval < 0.001)
307 fprintf (stderr, "Ignoring invalid interval: %s\n",
310 opt_interval = new_interval;
315 if (opt_srcaddr != NULL)
317 opt_srcaddr = strdup (optarg);
328 new_send_ttl = atoi (optarg);
329 if ((new_send_ttl > 0) && (new_send_ttl < 256))
330 opt_send_ttl = new_send_ttl;
332 fprintf (stderr, "Ignoring invalid TTL argument: %s\n",
340 new_send_tos = atoi (optarg);
341 if ((new_send_tos > 0) && (new_send_tos < 256))
342 opt_send_tos = new_send_tos;
344 fprintf (stderr, "Ignoring invalid TOS argument: %s\n",
350 usage_exit (argv[0], 0);
353 usage_exit (argv[0], 1);
358 } /* }}} read_options */
360 static void time_normalize (struct timespec *ts) /* {{{ */
362 while (ts->tv_nsec < 0)
371 ts->tv_nsec += 1000000000;
374 while (ts->tv_nsec >= 1000000000)
377 ts->tv_nsec -= 1000000000;
379 } /* }}} void time_normalize */
381 static void time_calc (struct timespec *ts_dest, /* {{{ */
382 const struct timespec *ts_int,
383 const struct timeval *tv_begin,
384 const struct timeval *tv_end)
386 ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
387 ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
388 time_normalize (ts_dest);
390 /* Assure that `(begin + interval) > end'.
391 * This may seem overly complicated, but `tv_sec' is of type `time_t'
392 * which may be `unsigned. *sigh* */
393 if ((tv_end->tv_sec > ts_dest->tv_sec)
394 || ((tv_end->tv_sec == ts_dest->tv_sec)
395 && ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
398 ts_dest->tv_nsec = 0;
402 ts_dest->tv_sec = ts_dest->tv_sec - tv_end->tv_sec;
403 ts_dest->tv_nsec = ts_dest->tv_nsec - (tv_end->tv_usec * 1000);
404 time_normalize (ts_dest);
405 } /* }}} void time_calc */
408 static int update_stats_from_context (ping_context_t *ctx) /* {{{ */
410 if ((ctx == NULL) || (ctx->window == NULL))
413 werase (ctx->window);
415 box (ctx->window, 0, 0);
416 wattron (ctx->window, A_BOLD);
417 mvwprintw (ctx->window, /* y = */ 0, /* x = */ 5,
419 wattroff (ctx->window, A_BOLD);
420 wprintw (ctx->window, "ping statistics ");
421 mvwprintw (ctx->window, /* y = */ 1, /* x = */ 2,
422 "%i packets transmitted, %i received, %.2f%% packet "
424 ctx->req_sent, ctx->req_rcvd,
425 context_get_packet_loss (ctx),
427 if (ctx->req_rcvd != 0)
432 average = context_get_average (ctx);
433 deviation = context_get_stddev (ctx);
435 mvwprintw (ctx->window, /* y = */ 2, /* x = */ 2,
436 "rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms",
443 wrefresh (ctx->window);
446 } /* }}} int update_stats_from_context */
448 static int on_resize (pingobj_t *ping) /* {{{ */
450 pingobj_iter_t *iter;
455 getmaxyx (stdscr, height, width);
456 if ((height < 1) || (width < 1))
459 main_win_height = height - (4 * host_num);
460 wresize (main_win, main_win_height, /* width = */ width);
461 /* Allow scrolling */
462 scrollok (main_win, TRUE);
463 /* wsetscrreg (main_win, 0, main_win_height - 1); */
464 /* Allow hardware accelerated scrolling. */
465 idlok (main_win, TRUE);
468 for (iter = ping_iterator_get (ping);
470 iter = ping_iterator_next (iter))
472 ping_context_t *context;
474 context = ping_iterator_get_context (iter);
478 if (context->window != NULL)
480 delwin (context->window);
481 context->window = NULL;
483 context->window = newwin (/* height = */ 4,
485 /* y = */ main_win_height + (4 * context->index),
492 static int check_resize (pingobj_t *ping) /* {{{ */
498 int key = wgetch (stdscr);
501 else if (key == KEY_RESIZE)
506 return (on_resize (ping));
509 } /* }}} int check_resize */
511 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
513 pingobj_iter_t *iter;
521 nodelay (stdscr, TRUE);
523 getmaxyx (stdscr, height, width);
524 if ((height < 1) || (width < 1))
527 if (has_colors () == TRUE)
530 init_pair (OPING_GREEN, COLOR_GREEN, /* default = */ 0);
531 init_pair (OPING_YELLOW, COLOR_YELLOW, /* default = */ 0);
532 init_pair (OPING_RED, COLOR_RED, /* default = */ 0);
535 main_win_height = height - (4 * host_num);
536 main_win = newwin (/* height = */ main_win_height,
538 /* y = */ 0, /* x = */ 0);
539 /* Allow scrolling */
540 scrollok (main_win, TRUE);
541 /* wsetscrreg (main_win, 0, main_win_height - 1); */
542 /* Allow hardware accelerated scrolling. */
543 idlok (main_win, TRUE);
544 wmove (main_win, /* y = */ main_win_height - 1, /* x = */ 0);
547 for (iter = ping_iterator_get (ping);
549 iter = ping_iterator_next (iter))
551 ping_context_t *context;
553 context = ping_iterator_get_context (iter);
557 if (context->window != NULL)
559 delwin (context->window);
560 context->window = NULL;
562 context->window = newwin (/* height = */ 4,
564 /* y = */ main_win_height + (4 * context->index),
569 /* Don't know what good this does exactly, but without this code
570 * "check_resize" will be called right after startup and *somehow*
571 * this leads to display errors. If we purge all initial characters
572 * here, the problem goes away. "wgetch" is non-blocking due to
573 * "nodelay" (see above). */
574 while (wgetch (stdscr) != ERR)
576 /* eat up characters */;
580 } /* }}} int pre_loop_hook */
582 static int pre_sleep_hook (pingobj_t *ping) /* {{{ */
584 return (check_resize (ping));
585 } /* }}} int pre_sleep_hook */
587 static int post_sleep_hook (pingobj_t *ping) /* {{{ */
589 return (check_resize (ping));
590 } /* }}} int pre_sleep_hook */
591 #else /* if !USE_NCURSES */
592 static int pre_loop_hook (pingobj_t *ping) /* {{{ */
594 pingobj_iter_t *iter;
596 for (iter = ping_iterator_get (ping);
598 iter = ping_iterator_next (iter))
603 ctx = ping_iterator_get_context (iter);
608 ping_iterator_get_info (iter, PING_INFO_DATA, NULL, &buffer_size);
610 printf ("PING %s (%s) %zu bytes of data.\n",
611 ctx->host, ctx->addr, buffer_size);
615 } /* }}} int pre_loop_hook */
617 static int pre_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
622 } /* }}} int pre_sleep_hook */
624 static int post_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
627 } /* }}} int post_sleep_hook */
630 static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
634 unsigned int sequence;
639 ping_context_t *context;
642 buffer_len = sizeof (latency);
643 ping_iterator_get_info (iter, PING_INFO_LATENCY,
644 &latency, &buffer_len);
647 buffer_len = sizeof (sequence);
648 ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
649 &sequence, &buffer_len);
652 buffer_len = sizeof (recv_ttl);
653 ping_iterator_get_info (iter, PING_INFO_RECV_TTL,
654 &recv_ttl, &buffer_len);
657 buffer_len = sizeof (recv_tos);
658 ping_iterator_get_info (iter, PING_INFO_RECV_TOS,
659 &recv_tos, &buffer_len);
662 ping_iterator_get_info (iter, PING_INFO_DATA,
665 context = (ping_context_t *) ping_iterator_get_context (iter);
668 # define HOST_PRINTF(...) wprintw(main_win, __VA_ARGS__)
670 # define HOST_PRINTF(...) printf(__VA_ARGS__)
677 context->latency_total += latency;
678 context->latency_total_square += (latency * latency);
680 if ((context->latency_max < 0.0) || (context->latency_max < latency))
681 context->latency_max = latency;
682 if ((context->latency_min < 0.0) || (context->latency_min > latency))
683 context->latency_min = latency;
686 if (has_colors () == TRUE)
688 int color = OPING_GREEN;
689 double average = context_get_average (context);
690 double stddev = context_get_stddev (context);
692 if ((latency < (average - (2 * stddev)))
693 || (latency > (average + (2 * stddev))))
695 else if ((latency < (average - stddev))
696 || (latency > (average + stddev)))
697 color = OPING_YELLOW;
699 HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i tos=0x%02"PRIx8
701 data_len, context->host, context->addr,
702 sequence, recv_ttl, recv_tos);
703 wattron (main_win, COLOR_PAIR(color));
704 HOST_PRINTF ("%.2f", latency);
705 wattroff (main_win, COLOR_PAIR(color));
706 HOST_PRINTF (" ms\n");
711 HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i tos=0x%02"PRIx8
714 context->host, context->addr,
715 sequence, recv_ttl, recv_tos, latency);
723 if (has_colors () == TRUE)
725 HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u ",
726 context->host, context->addr,
728 wattron (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
729 HOST_PRINTF ("timeout");
730 wattroff (main_win, COLOR_PAIR(OPING_RED) | A_BOLD);
736 HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u timeout\n",
737 context->host, context->addr,
745 update_stats_from_context (context);
748 } /* }}} void update_host_hook */
750 static int post_loop_hook (pingobj_t *ping) /* {{{ */
752 pingobj_iter_t *iter;
758 for (iter = ping_iterator_get (ping);
760 iter = ping_iterator_next (iter))
762 ping_context_t *context;
764 context = ping_iterator_get_context (iter);
766 printf ("\n--- %s ping statistics ---\n"
767 "%i packets transmitted, %i received, %.2f%% packet loss, time %.1fms\n",
768 context->host, context->req_sent, context->req_rcvd,
769 context_get_packet_loss (context),
770 context->latency_total);
772 if (context->req_rcvd != 0)
777 average = context_get_average (context);
778 deviation = context_get_stddev (context);
780 printf ("rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms\n",
781 context->latency_min,
783 context->latency_max,
787 ping_iterator_set_context (iter, NULL);
788 context_destroy (context);
792 } /* }}} int post_loop_hook */
794 int main (int argc, char **argv) /* {{{ */
797 pingobj_iter_t *iter;
799 struct sigaction sigint_action;
801 struct timeval tv_begin;
802 struct timeval tv_end;
803 struct timespec ts_wait;
804 struct timespec ts_int;
812 /* Save the old effective user id */
813 saved_set_uid = geteuid ();
814 /* Set the effective user ID to the real user ID without changing the
815 * saved set-user ID */
816 status = seteuid (getuid ());
819 fprintf (stderr, "Temporarily dropping privileges "
820 "failed: %s\n", strerror (errno));
825 optind = read_options (argc, argv);
827 #if !_POSIX_SAVED_IDS
828 /* Cannot temporarily drop privileges -> reject every file but "-". */
829 if ((opt_filename != NULL)
830 && (strcmp ("-", opt_filename) != 0)
831 && (getuid () != geteuid ()))
833 fprintf (stderr, "Your real and effective user IDs don't "
834 "match. Reading from a file (option '-f')\n"
835 "is therefore too risky. You can still read "
836 "from STDIN using '-f -' if you like.\n"
842 if ((optind >= argc) && (opt_filename == NULL)) {
843 usage_exit (argv[0], 1);
846 if ((ping = ping_construct ()) == NULL)
848 fprintf (stderr, "ping_construct failed\n");
852 if (ping_setopt (ping, PING_OPT_TTL, &opt_send_ttl) != 0)
854 fprintf (stderr, "Setting TTL to %i failed: %s\n",
855 opt_send_ttl, ping_get_error (ping));
858 if (ping_setopt (ping, PING_OPT_TOS, &opt_send_tos) != 0)
860 fprintf (stderr, "Setting TOS to %i failed: %s\n",
861 opt_send_tos, ping_get_error (ping));
868 temp_nsec = modf (opt_interval, &temp_sec);
869 ts_int.tv_sec = (time_t) temp_sec;
870 ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
872 /* printf ("ts_int = %i.%09li\n", (int) ts_int.tv_sec, ts_int.tv_nsec); */
875 if (opt_addrfamily != PING_DEF_AF)
876 ping_setopt (ping, PING_OPT_AF, (void *) &opt_addrfamily);
878 if (opt_srcaddr != NULL)
880 if (ping_setopt (ping, PING_OPT_SOURCE, (void *) opt_srcaddr) != 0)
882 fprintf (stderr, "Setting source address failed: %s\n",
883 ping_get_error (ping));
887 if (opt_device != NULL)
889 if (ping_setopt (ping, PING_OPT_DEVICE, (void *) opt_device) != 0)
891 fprintf (stderr, "Setting device failed: %s\n",
892 ping_get_error (ping));
896 if (opt_filename != NULL)
902 if (strcmp (opt_filename, "-") == 0)
904 infile = fdopen(0, "r");
906 infile = fopen(opt_filename, "r");
910 fprintf (stderr, "Opening %s failed: %s\n",
911 (strcmp (opt_filename, "-") == 0)
912 ? "STDIN" : opt_filename,
918 /* Regain privileges */
919 status = seteuid (saved_set_uid);
922 fprintf (stderr, "Temporarily re-gaining privileges "
923 "failed: %s\n", strerror (errno));
928 while (fgets(line, sizeof(line), infile))
930 /* Strip whitespace */
931 if (sscanf(line, "%s", host) != 1)
934 if ((host[0] == 0) || (host[0] == '#'))
937 if (ping_host_add(ping, host) < 0)
939 const char *errmsg = ping_get_error (ping);
941 fprintf (stderr, "Adding host `%s' failed: %s\n", host, errmsg);
951 /* Drop privileges */
952 status = seteuid (getuid ());
955 fprintf (stderr, "Temporarily dropping privileges "
956 "failed: %s\n", strerror (errno));
965 /* Regain privileges */
966 status = seteuid (saved_set_uid);
969 fprintf (stderr, "Temporarily re-gaining privileges "
970 "failed: %s\n", strerror (errno));
975 for (i = optind; i < argc; i++)
977 if (ping_host_add (ping, argv[i]) < 0)
979 const char *errmsg = ping_get_error (ping);
981 fprintf (stderr, "Adding host `%s' failed: %s\n", argv[i], errmsg);
990 /* Permanently drop root privileges if we're setuid-root. */
991 status = setuid (getuid ());
994 fprintf (stderr, "Dropping privileges failed: %s\n",
1000 saved_set_uid = (uid_t) -1;
1003 ping_initialize_contexts (ping);
1008 memset (&sigint_action, '\0', sizeof (sigint_action));
1009 sigint_action.sa_handler = sigint_handler;
1010 if (sigaction (SIGINT, &sigint_action, NULL) < 0)
1012 perror ("sigaction");
1016 pre_loop_hook (ping);
1018 while (opt_count != 0)
1023 if (gettimeofday (&tv_begin, NULL) < 0)
1025 perror ("gettimeofday");
1029 if (ping_send (ping) < 0)
1031 fprintf (stderr, "ping_send failed: %s\n",
1032 ping_get_error (ping));
1037 for (iter = ping_iterator_get (ping);
1039 iter = ping_iterator_next (iter))
1041 update_host_hook (iter, index);
1045 pre_sleep_hook (ping);
1047 /* Don't sleep in the last iteration */
1051 if (gettimeofday (&tv_end, NULL) < 0)
1053 perror ("gettimeofday");
1057 time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
1059 /* printf ("Sleeping for %i.%09li seconds\n", (int) ts_wait.tv_sec, ts_wait.tv_nsec); */
1060 while ((status = nanosleep (&ts_wait, &ts_wait)) != 0)
1064 perror ("nanosleep");
1067 else if (opt_count == 0)
1074 post_sleep_hook (ping);
1078 } /* while (opt_count != 0) */
1080 post_loop_hook (ping);
1082 ping_destroy (ping);
1085 } /* }}} int main */
1087 /* vim: set fdm=marker : */