Code

src/oping.c: Add ncurses based front-end.
[liboping.git] / src / oping.c
1 /**
2  * Object oriented C module to send ICMP and ICMPv6 `echo's.
3  * Copyright (C) 2006-2010  Florian octo Forster <octo at verplant.org>
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  */
20 #if HAVE_CONFIG_H
21 # include <config.h>
22 #endif
24 #if STDC_HEADERS
25 # include <stdlib.h>
26 # include <stdio.h>
27 # include <string.h>
28 # include <errno.h>
29 # include <assert.h>
30 #else
31 # error "You don't have the standard C99 header files installed"
32 #endif /* STDC_HEADERS */
34 #if HAVE_UNISTD_H
35 # include <unistd.h>
36 #endif
38 #if HAVE_MATH_H
39 # include <math.h>
40 #endif
42 #if TIME_WITH_SYS_TIME
43 # include <sys/time.h>
44 # include <time.h>
45 #else
46 # if HAVE_SYS_TIME_H
47 #  include <sys/time.h>
48 # else
49 #  include <time.h>
50 # endif
51 #endif
53 #if HAVE_NETDB_H
54 # include <netdb.h> /* NI_MAXHOST */
55 #endif
57 #if HAVE_SIGNAL_H
58 # include <signal.h>
59 #endif
61 #if HAVE_SYS_TYPES_H
62 #include <sys/types.h>
63 #endif
65 #if USE_NCURSES
66 # include <ncurses.h>
67 #endif
69 #include "oping.h"
71 #ifndef _POSIX_SAVED_IDS
72 # define _POSIX_SAVED_IDS 0
73 #endif
75 typedef struct ping_context
76 {
77         char host[NI_MAXHOST];
78         char addr[NI_MAXHOST];
80         int req_sent;
81         int req_rcvd;
83         double latency_min;
84         double latency_max;
85         double latency_total;
86         double latency_total_square;
88 #if USE_NCURSES
89         WINDOW *window;
90 #endif
91 } ping_context_t;
93 static double  opt_interval   = 1.0;
94 static int     opt_addrfamily = PING_DEF_AF;
95 static char   *opt_srcaddr    = NULL;
96 static char   *opt_device     = NULL;
97 static char   *opt_filename   = NULL;
98 static int     opt_count      = -1;
99 static int     opt_send_ttl   = 64;
101 #if USE_NCURSES
102 static WINDOW *main_win = NULL;
103 #endif
105 static void sigint_handler (int signal)
107         /* Make compiler happy */
108         signal = 0;
109         /* Exit the loop */
110         opt_count = 0;
113 static ping_context_t *context_create (void)
115         ping_context_t *ret;
117         if ((ret = malloc (sizeof (ping_context_t))) == NULL)
118                 return (NULL);
120         memset (ret, '\0', sizeof (ping_context_t));
122         ret->latency_min   = -1.0;
123         ret->latency_max   = -1.0;
124         ret->latency_total = 0.0;
125         ret->latency_total_square = 0.0;
127 #if USE_NCURSES
128         ret->window = NULL;
129 #endif
131         return (ret);
134 static void context_destroy (ping_context_t *context)
136         if (context == NULL)
137                 return;
139 #if USE_NCURSES
140         if (context->window != NULL)
141         {
142                 delwin (context->window);
143                 context->window = NULL;
144         }
145 #endif
147         free (context);
150 static void usage_exit (const char *name, int status)
152         int name_length;
154         name_length = (int) strlen (name);
156         fprintf (stderr, "Usage: %s [OPTIONS] "
157                                 "-f filename | host [host [host ...]]\n"
159                         "\nAvailable options:\n"
160                         "  -4|-6        force the use of IPv4 or IPv6\n"
161                         "  -c count     number of ICMP packets to send\n"
162                         "  -i interval  interval with which to send ICMP packets\n"
163                         "  -t ttl       time to live for each ICMP packet\n"
164                         "  -I srcaddr   source address\n"
165                         "  -D device    outgoing interface name\n"
166                         "  -f filename  filename to read hosts from\n"
168                         "\noping "PACKAGE_VERSION", http://verplant.org/liboping/\n"
169                         "by Florian octo Forster <octo@verplant.org>\n"
170                         "for contributions see `AUTHORS'\n",
171                         name);
172         exit (status);
175 static int read_options (int argc, char **argv)
177         int optchar;
179         while (1)
180         {
181                 optchar = getopt (argc, argv, "46c:hi:I:t:f:D:");
183                 if (optchar == -1)
184                         break;
186                 switch (optchar)
187                 {
188                         case '4':
189                         case '6':
190                                 opt_addrfamily = (optchar == '4') ? AF_INET : AF_INET6;
191                                 break;
193                         case 'c':
194                                 {
195                                         int new_count;
196                                         new_count = atoi (optarg);
197                                         if (new_count > 0)
198                                                 opt_count = new_count;
199                                         else
200                                                 fprintf(stderr, "Ignoring invalid count: %s\n",
201                                                                 optarg);
202                                 }
203                                 break;
205                         case 'f':
206                                 {
207                                         if (opt_filename != NULL)
208                                                 free (opt_filename);
209                                         opt_filename = strdup (optarg);
210                                 }
211                                 break;
213                         case 'i':
214                                 {
215                                         double new_interval;
216                                         new_interval = atof (optarg);
217                                         if (new_interval < 0.001)
218                                                 fprintf (stderr, "Ignoring invalid interval: %s\n",
219                                                                 optarg);
220                                         else
221                                                 opt_interval = new_interval;
222                                 }
223                                 break;
224                         case 'I':
225                                 {
226                                         if (opt_srcaddr != NULL)
227                                                 free (opt_srcaddr);
228                                         opt_srcaddr = strdup (optarg);
229                                 }
230                                 break;
232                         case 'D':
233                                 opt_device = optarg;
234                                 break;
236                         case 't':
237                         {
238                                 int new_send_ttl;
239                                 new_send_ttl = atoi (optarg);
240                                 if ((new_send_ttl > 0) && (new_send_ttl < 256))
241                                         opt_send_ttl = new_send_ttl;
242                                 else
243                                         fprintf (stderr, "Ignoring invalid TTL argument: %s\n",
244                                                         optarg);
245                                 break;
246                         }
248                         case 'h':
249                                 usage_exit (argv[0], 0);
250                                 break;
251                         default:
252                                 usage_exit (argv[0], 1);
253                 }
254         }
256         return (optind);
259 static void print_host (pingobj_iter_t *iter)
261         double          latency;
262         unsigned int    sequence;
263         int             recv_ttl;
264         size_t          buffer_len;
265         size_t          data_len;
266         ping_context_t *context;
268         latency = -1.0;
269         buffer_len = sizeof (latency);
270         ping_iterator_get_info (iter, PING_INFO_LATENCY,
271                         &latency, &buffer_len);
273         sequence = 0;
274         buffer_len = sizeof (sequence);
275         ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
276                         &sequence, &buffer_len);
278         recv_ttl = -1;
279         buffer_len = sizeof (recv_ttl);
280         ping_iterator_get_info (iter, PING_INFO_RECV_TTL,
281                         &recv_ttl, &buffer_len);
283         data_len = 0;
284         ping_iterator_get_info (iter, PING_INFO_DATA,
285                         NULL, &data_len);
287         context = (ping_context_t *) ping_iterator_get_context (iter);
289 #if USE_NCURSES
290 # define HOST_PRINTF(...) wprintw(main_win, __VA_ARGS__)
291 #else
292 # define HOST_PRINTF(...) printf(__VA_ARGS__)
293 #endif
295         context->req_sent++;
296         if (latency > 0.0)
297         {
298                 context->req_rcvd++;
299                 context->latency_total += latency;
300                 context->latency_total_square += (latency * latency);
302                 if ((context->latency_max < 0.0) || (context->latency_max < latency))
303                         context->latency_max = latency;
304                 if ((context->latency_min < 0.0) || (context->latency_min > latency))
305                         context->latency_min = latency;
307                 HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i "
308                                 "time=%.2f ms\n",
309                                 data_len,
310                                 context->host, context->addr,
311                                 sequence, recv_ttl, latency);
312         }
313         else
314         {
315                 HOST_PRINTF ("echo reply from %s (%s): icmp_seq=%u timeout\n",
316                                 context->host, context->addr,
317                                 sequence);
318         }
320 #if USE_NCURSES
321         wrefresh (main_win);
322         werase (context->window);
323         box (context->window, 0, 0);
324         mvwprintw (context->window, /* y = */ 0, /* x = */ 5,
325                         " %s ping statistics ",
326                         context->host);
327         mvwprintw (context->window, /* y = */ 1, /* x = */ 2,
328                         "%i packets transmitted, %i received, %.2f%% packet "
329                         "loss, time %.1fms",
330                         context->req_sent, context->req_rcvd,
331                         100.0 * (context->req_sent - context->req_rcvd) / ((double) context->req_sent),
332                         context->latency_total);
333         if (context->req_rcvd != 0)
334         {
335                 double num_total;
336                 double average;
337                 double deviation;
339                 num_total = (double) context->req_rcvd;
341                 average = context->latency_total / num_total;
342                 deviation = sqrt (((num_total * context->latency_total_square) - (context->latency_total * context->latency_total))
343                                 / (num_total * (num_total - 1.0)));
345                 mvwprintw (context->window, /* y = */ 2, /* x = */ 2,
346                                 "rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms",
347                                 context->latency_min,
348                                 average,
349                                 context->latency_max,
350                                 deviation);
351         }
352         wrefresh (context->window);
353 #endif
356 static void time_normalize (struct timespec *ts)
358         while (ts->tv_nsec < 0)
359         {
360                 if (ts->tv_sec == 0)
361                 {
362                         ts->tv_nsec = 0;
363                         return;
364                 }
366                 ts->tv_sec  -= 1;
367                 ts->tv_nsec += 1000000000;
368         }
370         while (ts->tv_nsec >= 1000000000)
371         {
372                 ts->tv_sec  += 1;
373                 ts->tv_nsec -= 1000000000;
374         }
377 static void time_calc (struct timespec *ts_dest, /* {{{ */
378                 const struct timespec *ts_int,
379                 const struct timeval  *tv_begin,
380                 const struct timeval  *tv_end)
382         ts_dest->tv_sec = tv_begin->tv_sec + ts_int->tv_sec;
383         ts_dest->tv_nsec = (tv_begin->tv_usec * 1000) + ts_int->tv_nsec;
384         time_normalize (ts_dest);
386         /* Assure that `(begin + interval) > end'.
387          * This may seem overly complicated, but `tv_sec' is of type `time_t'
388          * which may be `unsigned. *sigh* */
389         if ((tv_end->tv_sec > ts_dest->tv_sec)
390                         || ((tv_end->tv_sec == ts_dest->tv_sec)
391                                 && ((tv_end->tv_usec * 1000) > ts_dest->tv_nsec)))
392         {
393                 ts_dest->tv_sec  = 0;
394                 ts_dest->tv_nsec = 0;
395                 return;
396         }
398         ts_dest->tv_sec = ts_dest->tv_sec - tv_end->tv_sec;
399         ts_dest->tv_nsec = ts_dest->tv_nsec - (tv_end->tv_usec * 1000);
400         time_normalize (ts_dest);
401 } /* }}} void time_calc */
403 static int print_header (pingobj_t *ping) /* {{{ */
405         pingobj_iter_t *iter;
406         int i;
408 #if USE_NCURSES
409         initscr ();
410         cbreak ();
411         noecho ();
412 #endif
414         i = 0;
415         for (iter = ping_iterator_get (ping);
416                         iter != NULL;
417                         iter = ping_iterator_next (iter))
418         {
419                 ping_context_t *context;
420                 size_t buffer_size;
422                 context = context_create ();
424                 buffer_size = sizeof (context->host);
425                 ping_iterator_get_info (iter, PING_INFO_HOSTNAME, context->host, &buffer_size);
427                 buffer_size = sizeof (context->addr);
428                 ping_iterator_get_info (iter, PING_INFO_ADDRESS, context->addr, &buffer_size);
430                 buffer_size = 0;
431                 ping_iterator_get_info (iter, PING_INFO_DATA, NULL, &buffer_size);
433 #if USE_NCURSES
434                 context->window = newwin (/* height = */ 4, COLS,
435                                 /* start y = */ 4*i, /* start x = */ 0);
436                 box (context->window, 0, 0);
437                 mvwprintw (context->window, /* y = */ 0, /* x = */ 5,
438                                 " %s ping statistics ",
439                                 context->host);
440                 wrefresh (context->window);
441 #else /* !USE_NCURSES */
442                 printf ("PING %s (%s) %zu bytes of data.\n",
443                                 context->host, context->addr, buffer_size);
444 #endif
446                 ping_iterator_set_context (iter, (void *) context);
448                 i++;
449         }
451 #if USE_NCURSES
452         delwin (main_win);
453         main_win = newwin (LINES - 4*i, COLS, 4*i, 0);
454         scrollok (main_win, TRUE);
455 #endif
457         return (0);
458 } /* }}} int print_header */
460 static int print_footer (pingobj_t *ping)
462         pingobj_iter_t *iter;
464 #if USE_NCURSES
465         endwin ();
466 #endif
468         for (iter = ping_iterator_get (ping);
469                         iter != NULL;
470                         iter = ping_iterator_next (iter))
471         {
472                 ping_context_t *context;
474                 context = ping_iterator_get_context (iter);
476                 printf ("\n--- %s ping statistics ---\n"
477                                 "%i packets transmitted, %i received, %.2f%% packet loss, time %.1fms\n",
478                                 context->host, context->req_sent, context->req_rcvd,
479                                 100.0 * (context->req_sent - context->req_rcvd) / ((double) context->req_sent),
480                                 context->latency_total);
482                 if (context->req_rcvd != 0)
483                 {
484                         double num_total;
485                         double average;
486                         double deviation;
488                         num_total = (double) context->req_rcvd;
490                         average = context->latency_total / num_total;
491                         deviation = sqrt (((num_total * context->latency_total_square) - (context->latency_total * context->latency_total))
492                                         / (num_total * (num_total - 1.0)));
494                         printf ("rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms\n",
495                                         context->latency_min,
496                                         average,
497                                         context->latency_max,
498                                         deviation);
499                 }
501                 ping_iterator_set_context (iter, NULL);
502                 context_destroy (context);
503         }
505         return (0);
506 } /* }}} int print_footer */
508 int main (int argc, char **argv) /* {{{ */
510         pingobj_t      *ping;
511         pingobj_iter_t *iter;
513         struct sigaction sigint_action;
515         struct timeval  tv_begin;
516         struct timeval  tv_end;
517         struct timespec ts_wait;
518         struct timespec ts_int;
520         int optind;
521         int i;
522         int status;
523 #if _POSIX_SAVED_IDS
524         uid_t saved_set_uid;
526         /* Save the old effective user id */
527         saved_set_uid = geteuid ();
528         /* Set the effective user ID to the real user ID without changing the
529          * saved set-user ID */
530         status = seteuid (getuid ());
531         if (status != 0)
532         {
533                 fprintf (stderr, "Temporarily dropping privileges "
534                                 "failed: %s\n", strerror (errno));
535                 exit (EXIT_FAILURE);
536         }
537 #endif
539         optind = read_options (argc, argv);
541 #if !_POSIX_SAVED_IDS
542         /* Cannot temporarily drop privileges -> reject every file but "-". */
543         if ((opt_filename != NULL)
544                         && (strcmp ("-", opt_filename) != 0)
545                         && (getuid () != geteuid ()))
546         {
547                 fprintf (stderr, "Your real and effective user IDs don't "
548                                 "match. Reading from a file (option '-f')\n"
549                                 "is therefore too risky. You can still read "
550                                 "from STDIN using '-f -' if you like.\n"
551                                 "Sorry.\n");
552                 exit (EXIT_FAILURE);
553         }
554 #endif
556         if ((optind >= argc) && (opt_filename == NULL)) {
557                 usage_exit (argv[0], 1);
558         }
560         if ((ping = ping_construct ()) == NULL)
561         {
562                 fprintf (stderr, "ping_construct failed\n");
563                 return (1);
564         }
566         if (ping_setopt (ping, PING_OPT_TTL, &opt_send_ttl) != 0)
567         {
568                 fprintf (stderr, "Setting TTL to %i failed: %s\n",
569                                 opt_send_ttl, ping_get_error (ping));
570         }
572         {
573                 double temp_sec;
574                 double temp_nsec;
576                 temp_nsec = modf (opt_interval, &temp_sec);
577                 ts_int.tv_sec  = (time_t) temp_sec;
578                 ts_int.tv_nsec = (long) (temp_nsec * 1000000000L);
580                 /* printf ("ts_int = %i.%09li\n", (int) ts_int.tv_sec, ts_int.tv_nsec); */
581         }
583         if (opt_addrfamily != PING_DEF_AF)
584                 ping_setopt (ping, PING_OPT_AF, (void *) &opt_addrfamily);
586         if (opt_srcaddr != NULL)
587         {
588                 if (ping_setopt (ping, PING_OPT_SOURCE, (void *) opt_srcaddr) != 0)
589                 {
590                         fprintf (stderr, "Setting source address failed: %s\n",
591                                         ping_get_error (ping));
592                 }
593         }
595         if (opt_device != NULL)
596         {
597                 if (ping_setopt (ping, PING_OPT_DEVICE, (void *) opt_device) != 0)
598                 {
599                         fprintf (stderr, "Setting device failed: %s\n",
600                                         ping_get_error (ping));
601                 }
602         }
604         if (opt_filename != NULL)
605         {
606                 FILE *infile;
607                 char line[256];
608                 char host[256];
610                 if (strcmp (opt_filename, "-") == 0)
611                         /* Open STDIN */
612                         infile = fdopen(0, "r");
613                 else
614                         infile = fopen(opt_filename, "r");
616                 if (infile == NULL)
617                 {
618                         fprintf (stderr, "Opening %s failed: %s\n",
619                                         (strcmp (opt_filename, "-") == 0)
620                                         ? "STDIN" : opt_filename,
621                                         strerror(errno));
622                         return (1);
623                 }
625 #if _POSIX_SAVED_IDS
626                 /* Regain privileges */
627                 status = seteuid (saved_set_uid);
628                 if (status != 0)
629                 {
630                         fprintf (stderr, "Temporarily re-gaining privileges "
631                                         "failed: %s\n", strerror (errno));
632                         exit (EXIT_FAILURE);
633                 }
634 #endif
636                 while (fgets(line, sizeof(line), infile))
637                 {
638                         /* Strip whitespace */
639                         if (sscanf(line, "%s", host) != 1)
640                                 continue;
642                         if ((host[0] == 0) || (host[0] == '#'))
643                                 continue;
645                         if (ping_host_add(ping, host) < 0)
646                         {
647                                 const char *errmsg = ping_get_error (ping);
649                                 fprintf (stderr, "Adding host `%s' failed: %s\n", host, errmsg);
650                                 continue;
651                         }
652                 }
654 #if _POSIX_SAVED_IDS
655                 /* Drop privileges */
656                 status = seteuid (getuid ());
657                 if (status != 0)
658                 {
659                         fprintf (stderr, "Temporarily dropping privileges "
660                                         "failed: %s\n", strerror (errno));
661                         exit (EXIT_FAILURE);
662                 }
663 #endif
665                 fclose(infile);
666         }
668 #if _POSIX_SAVED_IDS
669         /* Regain privileges */
670         status = seteuid (saved_set_uid);
671         if (status != 0)
672         {
673                 fprintf (stderr, "Temporarily re-gaining privileges "
674                                 "failed: %s\n", strerror (errno));
675                 exit (EXIT_FAILURE);
676         }
677 #endif
679         for (i = optind; i < argc; i++)
680         {
681                 if (ping_host_add (ping, argv[i]) < 0)
682                 {
683                         const char *errmsg = ping_get_error (ping);
685                         fprintf (stderr, "Adding host `%s' failed: %s\n", argv[i], errmsg);
686                         continue;
687                 }
688         }
690         /* Permanently drop root privileges if we're setuid-root. */
691         status = setuid (getuid ());
692         if (status != 0)
693         {
694                 fprintf (stderr, "Dropping privileges failed: %s\n",
695                                 strerror (errno));
696                 exit (EXIT_FAILURE);
697         }
699 #if _POSIX_SAVED_IDS
700         saved_set_uid = (uid_t) -1;
701 #endif
703         print_header (ping);
705         if (i == 0)
706                 return (1);
708         memset (&sigint_action, '\0', sizeof (sigint_action));
709         sigint_action.sa_handler = sigint_handler;
710         if (sigaction (SIGINT, &sigint_action, NULL) < 0)
711         {
712                 perror ("sigaction");
713                 return (1);
714         }
716         while (opt_count != 0)
717         {
718                 int status;
720                 if (gettimeofday (&tv_begin, NULL) < 0)
721                 {
722                         perror ("gettimeofday");
723                         return (1);
724                 }
726                 if (ping_send (ping) < 0)
727                 {
728                         fprintf (stderr, "ping_send failed: %s\n",
729                                         ping_get_error (ping));
730                         return (1);
731                 }
733                 for (iter = ping_iterator_get (ping);
734                                 iter != NULL;
735                                 iter = ping_iterator_next (iter))
736                 {
737                         print_host (iter);
738                 }
739                 fflush (stdout);
741                 /* Don't sleep in the last iteration */
742                 if (opt_count == 1)
743                         break;
745                 if (gettimeofday (&tv_end, NULL) < 0)
746                 {
747                         perror ("gettimeofday");
748                         return (1);
749                 }
751                 time_calc (&ts_wait, &ts_int, &tv_begin, &tv_end);
753                 /* printf ("Sleeping for %i.%09li seconds\n", (int) ts_wait.tv_sec, ts_wait.tv_nsec); */
754                 while ((status = nanosleep (&ts_wait, &ts_wait)) != 0)
755                 {
756                         if (errno != EINTR)
757                         {
758                                 perror ("nanosleep");
759                                 break;
760                         }
761                         else if (opt_count == 0)
762                         {
763                                 /* sigint */
764                                 break;
765                         }
766                 }
768                 if (opt_count > 0)
769                         opt_count--;
770         } /* while (opt_count != 0) */
772         print_footer (ping);
774         ping_destroy (ping);
776         return (0);
777 } /* }}} int main */
779 /* vim: set fdm=marker : */