Merge remote-tracking branch 'github/master' into lh/performance
authorFlorian Forster <ff@octo.it>
Fri, 5 May 2017 06:43:52 +0000 (08:43 +0200)
committerFlorian Forster <ff@octo.it>
Fri, 5 May 2017 08:46:44 +0000 (10:46 +0200)
25 files changed:
.gitignore
ChangeLog
Makefile.am
README
autogen.sh [new file with mode: 0755]
bindings/perl/Makefile.PL
bindings/perl/Oping.xs
bindings/perl/README
bindings/perl/lib/Net/Oping.pm
configure.ac
src/Makefile.am
src/liboping.c
src/liboping.pc.in [new file with mode: 0644]
src/mans/liboping.pod
src/mans/oping.pod
src/mans/ping_construct.pod
src/mans/ping_get_error.pod
src/mans/ping_host_add.pod
src/mans/ping_iterator_get.pod
src/mans/ping_iterator_get_context.pod
src/mans/ping_iterator_get_info.pod
src/mans/ping_send.pod
src/mans/ping_setopt.pod
src/oping.c
src/oping.h

index 53ca1f5..b9cb926 100644 (file)
@@ -12,6 +12,7 @@ depcomp
 install-sh
 missing
 stamp-h1
+/m4/
 
 # libtool stuff
 libltdl
@@ -19,13 +20,15 @@ libtool
 ltmain.sh
 
 # build output
-.libs
+.libs/
 bindings/perl/Oping.c
 bindings/perl/blib
 bindings/perl/pm_to_blib
 src/mans/*.3
 src/mans/*.8
 src/oping
+src/noping
+src/liboping.pc
 *.bs
 *.la
 *.lo
@@ -33,3 +36,4 @@ src/oping
 *.o
 *.tar.gz
 *.tar.bz2
+bindings/perl/MYMETA.yml
index e895bbe..7d85b14 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,40 @@
+2016-06-27, Version 1.9.0:
+       * liboping: The new "PING_OPT_MARK" option allows to mark packets,
+         which can be used for filtering and routing such packets on Linux.
+       * oping, noping: The new "-m" command line option allows to set a mark
+         on packets sent by the tool.
+       * oping, noping: The new "-O" command line option allows to write
+         measurements to an CSV file.
+       * oping, noping: The new "-w" command line option allows to specify
+         the timeout after which a packet/reply is considered "dropped".
+
+2014-11-20, Version 1.8.0:
+       * oping, noping: Average and standard deviation have been removed from
+         the status output, which show median and 95th percentile instead.
+         The percentile can be chosen with the "-P" option.
+       * noping: The additional graph types "histogram" and "boxplot" have
+         been added, which can be selected with the "-g" option.
+
+2014-09-25, Version 1.7.0:
+       * oping, noping: The new -Z option allows the exit status to indicate
+         the number of failing hosts. Thanks to Barak Pearlmutter for the
+         patch.
+       * noping: The ability to print a "prettyping" style graph has been
+         added. Thanks to Antoine Beaupré for his work!
+       * src/liboping.c: Build issues on Solaris have been fixed. Thanks
+         Scott Severtson for the fix!
+       * Build system: Creation and installation of a pkg-config file has
+         been added. Thanks to Barak Pearlmutter for the patch.
+
+2012-01-31, Version 1.6.2:
+       * Build system: Setting capabilities and the set-UID bit has been made
+         more fault-tolerant, so that it will work with Debian's fakeroot(1)
+         utility.
+       * src/liboping.c: Fixed a compiler warning about an non-static format
+         string. Thanks to Brian Edwards for pointing this out.
+       * src/liboping.c: Fixed compilation under Mac OS X and Solaris. Thanks
+         to Clayton O'Neill for his patch.
+
 2011-03-06, Version 1.6.1:
        * Build system: If "make install" is executed as root, the CAP_NET_RAW
          capability is added to the binary (on Linux) or the set-UID bit is
index af22243..6993205 100644 (file)
@@ -1,4 +1,3 @@
 SUBDIRS = src bindings
 
-dist-hook:
-       find $(distdir) -type d -name '.svn' | xargs rm -rf
+ACLOCAL_AMFLAGS = -I m4
diff --git a/README b/README
index 5488fb4..8611d7c 100644 (file)
--- a/README
+++ b/README
@@ -1,6 +1,6 @@
  liboping – Library to ping IPv4 and IPv6 hosts in parallel
 ════════════════════════════════════════════════════════════
-http://verplant.org/liboping/
+http://noping.cc/
 
 About
 ━━━━━
@@ -43,6 +43,14 @@ Permissions
   (which is strongly suggested), you won't be able to use the binaries as a
   normal user, because you won't have the permission to open raw sockets.
 
+  The “install” target will automatically try fix this, if it is run with UID~0
+  (as user root).  When on Linux, the capabilities described below will be
+  added. On other UNIXes the traditional Set-UID method (also described below)
+  is used instead. The build system will not abort if this fails, because there
+  are file systems which do not support either method. Also, the Debian
+  packaging system and possibly other scenarios only act as if they were
+  running as root.
+
   Linux
   ━━━━━
   On Linux, the preferred method is to assign the required “capability” to the
diff --git a/autogen.sh b/autogen.sh
new file mode 100755 (executable)
index 0000000..6a5d62d
--- /dev/null
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+set -e
+
+autoreconf --warnings=all --install
+echo "autoconfiguration done, to build: ./configure ; make"
index 5525dd0..98f93d8 100644 (file)
@@ -123,7 +123,7 @@ WriteMakefile(
     PREREQ_PM         => {},
     ($] >= 5.005
      ? (ABSTRACT_FROM => 'lib/Net/Oping.pm',
-        AUTHOR        => 'Florian Forster <octo@verplant.org>')
+        AUTHOR        => 'Florian Forster <ff@octo.it>')
      : ()),
     ($OPING_DEPEND ? (depend => $OPING_DEPEND) : ()),
     LIBS              => [$OPING_LIBS],
index 5731d3d..c88e3dc 100644 (file)
@@ -19,7 +19,7 @@
  *
  * Authors:
  *   Olivier Fredj <ofredj at proxad.net>
- *   Florian octo Forster <octo at verplant.org>
+ *   Florian octo Forster <ff at octo.it>
  */
 #include "EXTERN.h"
 #include "perl.h"
index 4d51a96..040a38a 100644 (file)
@@ -28,13 +28,13 @@ INSTALLATION
 DEPENDENCIES
 
   This module requires the "oping" library to be installed. The library is
-  available at <http://verplant.org/liboping/>.
+  available at <http://noping.cc/>.
 
 COPYRIGHT AND LICENSE
 
   Copyright (C) 2007 by Olivier Fredj <ofredj at proxad.net>
 
-  Copyright (C) 2008,2009 by Florian Forster <octo at verplant.org>
+  Copyright (C) 2008,2009 by Florian Forster <ff at octo.it>
 
   This library is free software; you can redistribute it and/or modify it
   under the same terms as Perl itself, either Perl version 5.8.7 or, at your
index bc109dc..ba67f73 100644 (file)
@@ -19,7 +19,7 @@
 #
 # Authors:
 #   Olivier Fredj <ofredj at proxad.net>
-#   Florian octo Forster <octo at verplant.org>
+#   Florian octo Forster <ff at octo.it>
 #
 
 package Net::Oping;
@@ -41,11 +41,11 @@ Net::Oping - ICMP latency measurement module using the oping library.
 =head1 DESCRIPTION
 
 This Perl module is a high-level interface to the
-L<oping library|http://verplant.org/liboping/>. Its purpose it to send
-C<ICMP ECHO_REQUEST> packets (also known as "ping") to a host and measure the
-time that elapses until the reception of an C<ICMP ECHO_REPLY> packet (also
-known as "pong"). If no such packet is received after a certain timeout the
-host is considered to be unreachable.
+L<oping library|http://noping.cc/>. Its purpose it to send C<ICMP ECHO_REQUEST>
+packets (also known as "ping") to a host and measure the time that elapses
+until the reception of an C<ICMP ECHO_REPLY> packet (also known as "pong"). If
+no such packet is received after a certain timeout the host is considered to be
+unreachable.
 
 The used I<oping> library supports "ping"ing multiple hosts in parallel and
 works with IPv4 and IPv6 transparently. Other advanced features that are
@@ -432,7 +432,7 @@ superuser or, under Linux, needs the C<CAP_NET_RAW> capability.
 
 L<liboping(3)>
 
-The I<liboping> homepage may be found at L<http://verplant.org/liboping/>.
+The I<liboping> homepage may be found at L<http://noping.cc/>.
 Information about its mailing list may be found at
 L<http://mailman.verplant.org/listinfo/liboping>.
 
@@ -445,8 +445,7 @@ Perl interface by Florian Forster.
 
 Copyright (C) 2007 by Olivier Fredj E<lt>ofredjE<nbsp>atE<nbsp>proxad.netE<gt>
 
-Copyright (C) 2008,2009 by Florian Forster
-E<lt>octoE<nbsp>atE<nbsp>verplant.orgE<gt>
+Copyright (C) 2008,2009 by Florian Forster E<lt>ffE<nbsp>atE<nbsp>octo.itE<gt>
 
 This library is free software; you can redistribute it and/or modify
 it under the same terms as Perl itself, either Perl version 5.8.7 or,
index a4eb289..60138fc 100644 (file)
@@ -1,8 +1,16 @@
-AC_INIT(liboping, 1.6.1)
-AC_CONFIG_SRCDIR(src/liboping.c)
-AC_CONFIG_HEADERS(src/config.h)
-AM_INIT_AUTOMAKE(dist-bzip2)
-AC_LANG(C)
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.65])
+AC_INIT([liboping],
+       [1.9.0],
+       [liboping@verplant.org],
+       [],
+       [http://noping.cc/])
+AC_CONFIG_SRCDIR([src/liboping.c])
+AC_CONFIG_HEADERS([src/config.h])
+AC_CONFIG_MACRO_DIR([m4])
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_LANG([C])
 
 AC_PREFIX_DEFAULT("/opt/oping")
 
@@ -16,7 +24,7 @@ AC_SUBST(LIBOPING_PATCH)
 
 # ABI version
 LIBOPING_CURRENT=2
-LIBOPING_REVISION=7
+LIBOPING_REVISION=11
 LIBOPING_AGE=2
 AC_SUBST(LIBOPING_CURRENT)
 AC_SUBST(LIBOPING_REVISION)
@@ -38,26 +46,22 @@ then
 fi
 AC_ARG_VAR(PERL, [Perl interpreter command])
 
-#
 # configure libtool
-#
-AC_LIBTOOL_DLOPEN
-AC_PROG_LIBTOOL
-#AC_PROG_RANLIB
+LT_INIT([dlopen])
+
+# pkg-config interface
+PKG_INSTALLDIR
+
+AC_ARG_WITH(pkgconfigdir,
+           AC_HELP_STRING([--with-pkgconfigdir], [Use the specified pkgconfig dir (default is libdir/pkgconfig)]),
+           [pkgconfigdir="${withval}"],
+           [pkgconfigdir='${libdir}/pkgconfig'])
+AC_SUBST([pkgconfigdir])
 
-#
 # Checks for header files.
-#
 AC_HEADER_STDC
-AC_CHECK_HEADERS(unistd.h)
-AC_CHECK_HEADERS(math.h)
-AC_CHECK_HEADERS(fcntl.h)
-AC_CHECK_HEADERS(sys/types.h)
-AC_CHECK_HEADERS(sys/stat.h)
 AC_HEADER_TIME
-AC_CHECK_HEADERS(sys/socket.h)
-AC_CHECK_HEADERS(netdb.h)
-AC_CHECK_HEADERS(signal.h)
+AC_CHECK_HEADERS([math.h signal.h fcntl.h inttypes.h netdb.h stdint.h stdlib.h string.h sys/socket.h sys/time.h unistd.h locale.h langinfo.h])
 
 # This sucks, but what can I do..?
 AC_CHECK_HEADERS(netinet/in_systm.h, [], [],
@@ -162,12 +166,24 @@ AC_CHECK_HEADERS(netinet/icmp6.h, [], [],
 #endif
 ])
 
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_SIZE_T
+AC_TYPE_SSIZE_T
+AC_TYPE_UID_T
+AC_TYPE_UINT16_T
+AC_TYPE_UINT32_T
+AC_TYPE_UINT8_T
+
+LIBOPING_PC_LIBS_PRIVATE=''
+
 socket_needs_socket="no"
 AC_CHECK_FUNCS(socket, [],
        AC_CHECK_LIB(socket, socket,
                [socket_needs_socket="yes"],
                AC_MSG_ERROR(cannot find socket)))
-AM_CONDITIONAL(BUILD_WITH_LIBSOCKET, test "x$socket_needs_socket" = "xyes")
+if test "x$socket_needs_socket" = "xyes"; then
+       LIBOPING_PC_LIBS_PRIVATE="${LIBOPING_PC_LIBS_PRIVATE} -lsocket"
+fi
 
 # Under Solaris, the `xnet' library provides `recvmsg' which complies with the
 # X/Open CAE Specification.
@@ -177,22 +193,39 @@ if test "x$with_libxnet" = "xyes"
 then
        CPPFLAGS="$CPPFLAGS -D_XPG4_2 -D__EXTENSIONS__"
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBXNET, test "x$with_libxnet" = "xyes")
-
-nanosleep_needs_rt="no"
-AC_CHECK_FUNCS(nanosleep, [],
-       AC_CHECK_LIB(rt, nanosleep,
-               [nanosleep_needs_rt="yes"],
-               AC_MSG_ERROR(cannot find nanosleep)))
-AM_CONDITIONAL(BUILD_WITH_LIBRT, test "x$nanosleep_needs_rt" = "xyes")
-
-with_ncurses="yes"
-AC_CHECK_HEADERS(ncurses.h, [with_ncurses="yes"], [with_ncurses="no"])
-if test "x$with_ncurses" = "xyes"
-then
-       AC_CHECK_LIB(ncurses, mvwprintw, [with_ncurses="yes"], [with_ncurses="no"])
+if test "x$with_libxnet" = "xyes"; then
+       LIBOPING_PC_LIBS_PRIVATE="${LIBOPING_PC_LIBS_PRIVATE} -lxnet"
 fi
-AM_CONDITIONAL(BUILD_WITH_LIBNCURSES, test "x$with_ncurses" = "xyes")
+
+AC_SUBST(LIBOPING_PC_LIBS_PRIVATE)
+
+AC_SEARCH_LIBS([nanosleep],[rt],[],
+               [AC_MSG_ERROR([cannot find nanosleep])])
+
+AC_ARG_WITH(ncurses, AS_HELP_STRING([--with-ncurses], [Build oping CLI tool with ncurses support]))
+AS_IF([test "x$with_ncurses" != "xno"], [
+       can_build_with_ncurses="no"
+       PKG_CHECK_MODULES([NCURSES], [ncursesw], [can_build_with_ncurses=yes], [
+               PKG_CHECK_MODULES([NCURSES], [ncurses], [can_build_with_ncurses=yes], [
+                       AC_CHECK_LIB(ncursesw, mvwprintw, [NCURSES_LIBS="-lncursesw"; can_build_with_ncurses=yes], [
+                               AC_CHECK_LIB(ncurses, mvwprintw, [NCURSES_LIBS="-lncurses"; can_build_with_ncurses=yes])
+                       ])
+               ])
+       ])
+
+       AS_IF([test "x$can_build_with_ncurses" = "xyes"], [
+               AC_CHECK_HEADERS([ncursesw/curses.h ncursesw.h ncurses/curses.h ncurses.h], [can_build_with_ncurses=yes; break;], [can_build_with_ncurses=no])
+       ])
+
+       AS_IF([test "x$can_build_with_ncurses" = "xno" && test "x$with_ncurses" = "xyes"], [
+               AC_MSG_ERROR([ncurses not found but explicit enabled])
+       ],
+       [test "x$can_build_with_ncurses" = "xno"], [
+               AC_MSG_WARN([Will not build oping with ncurses support -- no suiteable ncurses installation found])
+       ])
+])
+
+AM_CONDITIONAL(BUILD_WITH_LIBNCURSES, test "x$with_ncurses" != "xno" && test "x$can_build_with_ncurses" = "xyes")
 
 AC_FUNC_STRERROR_R
 
@@ -232,4 +265,10 @@ AC_SUBST(PERL_BINDINGS_OPTIONS)
 
 AC_SUBST(BINDINGS)
 
-AC_OUTPUT(Makefile src/Makefile src/mans/Makefile bindings/Makefile)
+# Checks for library functions.
+AC_FUNC_MALLOC
+AC_FUNC_STRERROR_R
+AC_CHECK_FUNCS([gettimeofday memset modf select socket sqrt strcasecmp strdup strerror strncasecmp strtoul])
+
+AC_CONFIG_FILES([Makefile src/Makefile src/liboping.pc src/mans/Makefile bindings/Makefile])
+AC_OUTPUT
index 1326783..b8571aa 100644 (file)
@@ -25,31 +25,23 @@ liboping_la_SOURCES = oping.h liboping.c
 
 liboping_la_CPPFLAGS = $(AM_CPPFLAGS)
 liboping_la_LDFLAGS = $(AM_LDFLAGS) -version-info @LIBOPING_CURRENT@:@LIBOPING_REVISION@:@LIBOPING_AGE@
-liboping_la_LIBADD =
-if BUILD_WITH_LIBSOCKET
-liboping_la_LIBADD += -lsocket
-endif
-if BUILD_WITH_LIBXNET
-liboping_la_LIBADD += -lxnet
-endif
+liboping_la_LIBADD = $(LIBOPING_PC_LIBS_PRIVATE)
+
+pkgconfig_DATA = liboping.pc
+
+MOSTLYCLEANFILES = $(pkgconfig_DATA)
 
 bin_PROGRAMS = oping
 
 oping_SOURCES = oping.c
 oping_LDADD = liboping.la -lm
-if BUILD_WITH_LIBRT
-oping_LDADD += -lrt
-endif
 
 if BUILD_WITH_LIBNCURSES
 bin_PROGRAMS += noping
 
 noping_SOURCES = oping.c
-noping_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_NCURSES=1
-noping_LDADD = liboping.la -lm -lncurses
-if BUILD_WITH_LIBRT
-noping_LDADD += -lrt
-endif
+noping_CPPFLAGS = $(AM_CPPFLAGS) -DUSE_NCURSES=1 $(NCURSES_CFLAGS)
+noping_LDADD = liboping.la -lm $(NCURSES_LIBS)
 endif # BUILD_WITH_LIBNCURSES
 
 install-exec-hook:
index 776f24f..31b8813 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * Object oriented C module to send ICMP and ICMPv6 `echo's.
- * Copyright (C) 2006-2011  Florian octo Forster <ff at octo.it>
+ * Copyright (C) 2006-2016  Florian octo Forster <ff at octo.it>
  *
  * This library is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by the
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#ifdef __APPLE__
+#define __APPLE_USE_RFC_3542
+#endif
+
 #if HAVE_CONFIG_H
 # include <config.h>
 #endif
@@ -142,10 +146,13 @@ struct pingobj
 
        char                    *device;
 
+       char                    set_mark;
+       int                     mark;
+
        char                     errmsg[PING_ERRMSG_LEN];
 
        pinghost_t              *head;
-       pinghost_t              *table[PING_TABLE_LEN];
+       pinghost_t              *table[PING_TABLE_LEN];
 };
 
 /*
@@ -296,23 +303,20 @@ static pinghost_t *ping_receive_ipv4 (pingobj_t *obj, char *buffer,
        buffer     += ip_hdr_len;
        buffer_len -= ip_hdr_len;
 
-       if (buffer_len < sizeof (struct icmp))
+       if (buffer_len < ICMP_MINLEN)
                return (NULL);
 
        icmp_hdr = (struct icmp *) buffer;
-       buffer     += sizeof (struct icmp);
-       buffer_len -= sizeof (struct icmp);
-
        if (icmp_hdr->icmp_type != ICMP_ECHOREPLY)
        {
-               dprintf ("Unexpected ICMP type: %i\n", icmp_hdr->icmp_type);
+               dprintf ("Unexpected ICMP type: %"PRIu8"\n", icmp_hdr->icmp_type);
                return (NULL);
        }
 
        recv_checksum = icmp_hdr->icmp_cksum;
+       /* This writes to buffer. */
        icmp_hdr->icmp_cksum = 0;
-       calc_checksum = ping_icmp4_checksum ((char *) icmp_hdr,
-                       sizeof (struct icmp) + buffer_len);
+       calc_checksum = ping_icmp4_checksum (buffer, buffer_len);
 
        if (recv_checksum != calc_checksum)
        {
@@ -389,12 +393,12 @@ static pinghost_t *ping_receive_ipv6 (pingobj_t *obj, char *buffer,
 
        pinghost_t *ptr;
 
-       if (buffer_len < sizeof (struct icmp6_hdr))
+       if (buffer_len < ICMP_MINLEN)
                return (NULL);
 
        icmp_hdr = (struct icmp6_hdr *) buffer;
-       buffer     += sizeof (struct icmp);
-       buffer_len -= sizeof (struct icmp);
+       buffer     += ICMP_MINLEN;
+       buffer_len -= ICMP_MINLEN;
 
        if (icmp_hdr->icmp6_type != ICMP6_ECHO_REPLY)
        {
@@ -545,6 +549,7 @@ static int ping_receive_one (pingobj_t *obj, struct timeval *now, int addrfam)
                                                sizeof (recv_qos));
                                dprintf ("TOSv6 = 0x%02"PRIx8";\n", recv_qos);
                        } else
+#ifdef IPV6_HOPLIMIT
                        if (cmsg->cmsg_type == IPV6_HOPLIMIT)
                        {
                                memcpy (&recv_ttl, CMSG_DATA (cmsg),
@@ -552,6 +557,25 @@ static int ping_receive_one (pingobj_t *obj, struct timeval *now, int addrfam)
                                dprintf ("TTLv6 = %i;\n", recv_ttl);
                        }
                        else
+#endif
+#ifdef IPV6_UNICAST_HOPS
+                       if (cmsg->cmsg_type == IPV6_UNICAST_HOPS)
+                       {
+                               memcpy (&recv_ttl, CMSG_DATA (cmsg),
+                                               sizeof (recv_ttl));
+                               dprintf ("TTLv6 = %i;\n", recv_ttl);
+                       }
+                       else
+#endif
+#ifdef IPV6_MULTICAST_HOPS
+                       if (cmsg->cmsg_type == IPV6_MULTICAST_HOPS)
+                       {
+                               memcpy (&recv_ttl, CMSG_DATA (cmsg),
+                                               sizeof (recv_ttl));
+                               dprintf ("TTLv6 = %i;\n", recv_ttl);
+                       }
+                       else
+#endif
                        {
                                dprintf ("Not handling option %i.\n",
                                                cmsg->cmsg_type);
@@ -655,29 +679,28 @@ static int ping_send_one_ipv4 (pingobj_t *obj, pinghost_t *ph, int fd)
        struct icmp *icmp4;
        int status;
 
-       char buf[4096];
-       int  buflen;
+       char   buf[4096] = {0};
+       size_t buflen;
 
        char *data;
-       int   datalen;
+       size_t datalen;
 
        dprintf ("ph->hostname = %s\n", ph->hostname);
 
-       memset (buf, '\0', sizeof (buf));
        icmp4 = (struct icmp *) buf;
-       data  = (char *) (icmp4 + 1);
-
-       icmp4->icmp_type  = ICMP_ECHO;
-       icmp4->icmp_code  = 0;
-       icmp4->icmp_cksum = 0;
-       icmp4->icmp_id    = htons (ph->ident);
-       icmp4->icmp_seq   = htons (ph->sequence);
+       *icmp4 = (struct icmp) {
+               .icmp_type = ICMP_ECHO,
+               .icmp_id   = htons (ph->ident),
+               .icmp_seq  = htons (ph->sequence),
+       };
 
-       buflen = 4096 - sizeof (struct icmp);
-       strncpy (data, ph->data, buflen);
-       datalen = strlen (data);
+       datalen = strlen (ph->data);
+       buflen = ICMP_MINLEN + datalen;
+       if (sizeof (buf) < buflen)
+               return (EINVAL);
 
-       buflen = datalen + sizeof (struct icmp);
+       data  = buf + ICMP_MINLEN;
+       memcpy (data, ph->data, datalen);
 
        icmp4->icmp_cksum = ping_icmp4_checksum (buf, buflen);
 
@@ -700,7 +723,7 @@ static int ping_send_one_ipv6 (pingobj_t *obj, pinghost_t *ph, int fd)
        struct icmp6_hdr *icmp6;
        int status;
 
-       char buf[4096];
+       char buf[4096] = {0};
        int  buflen;
 
        char *data;
@@ -708,23 +731,22 @@ static int ping_send_one_ipv6 (pingobj_t *obj, pinghost_t *ph, int fd)
 
        dprintf ("ph->hostname = %s\n", ph->hostname);
 
-       memset (buf, '\0', sizeof (buf));
        icmp6 = (struct icmp6_hdr *) buf;
-       data  = (char *) (icmp6 + 1);
+       *icmp6 = (struct icmp6_hdr) {
+               .icmp6_type  = ICMP6_ECHO_REQUEST,
+               .icmp6_id    = htons (ph->ident),
+               .icmp6_seq   = htons (ph->sequence),
+       };
 
-       icmp6->icmp6_type  = ICMP6_ECHO_REQUEST;
-       icmp6->icmp6_code  = 0;
-       /* The checksum will be calculated by the TCP/IP stack.  */
-       /* FIXME */
-       icmp6->icmp6_cksum = 0;
-       icmp6->icmp6_id    = htons (ph->ident);
-       icmp6->icmp6_seq   = htons (ph->sequence);
+       datalen = strlen (ph->data);
+       buflen = sizeof (*icmp6) + datalen;
+       if (sizeof (buf) < buflen)
+               return (EINVAL);
 
-       buflen = 4096 - sizeof (struct icmp6_hdr);
-       strncpy (data, ph->data, buflen);
-       datalen = strlen (data);
+       data  = buf + ICMP_MINLEN;
+       memcpy (data, ph->data, datalen);
 
-       buflen = datalen + sizeof (struct icmp6_hdr);
+       /* The checksum will be calculated by the TCP/IP stack. */
 
        dprintf ("Sending ICMPv6 package with ID 0x%04x\n", ph->ident);
 
@@ -974,6 +996,15 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
                ping_set_errno (obj, errno);
                return -1;
        }
+       else if (fd >= FD_SETSIZE)
+       {
+               dprintf ("socket(2) returned file descriptor %d, which is above the file "
+                        "descriptor limit for select(2) (FD_SETSIZE = %d)\n",
+                        fd, FD_SETSIZE);
+               close (fd);
+               ping_set_errno (obj, EMFILE);
+               return -1;
+       }
 
        if (obj->srcaddr != NULL)
        {
@@ -1010,6 +1041,23 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
                }
        }
 #endif /* SO_BINDTODEVICE */
+#ifdef SO_MARK
+       if (obj->set_mark)
+       {
+               if (setsockopt(fd, SOL_SOCKET, SO_MARK,
+                               &obj->mark, sizeof(obj->mark)) != 0)
+               {
+                       ping_set_errno (obj, errno);
+#if WITH_DEBUG
+                       char errbuf[PING_ERRMSG_LEN];
+                       dprintf ("setsockopt (SO_MARK): %s\n",
+                                sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif
+                       close (fd);
+                       return -1;
+               }
+       }
+#endif
 #ifdef SO_TIMESTAMP
        if (1) /* {{{ */
        {
@@ -1037,10 +1085,12 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
        {
                int opt;
 
+#ifdef IP_RECVTOS
                /* Enable receiving the TOS field */
                opt = 1;
                setsockopt (fd, IPPROTO_IP, IP_RECVTOS,
                                &opt, sizeof (opt));
+#endif /* IP_RECVTOS */
 
                /* Enable receiving the TTL field */
                opt = 1;
@@ -1285,6 +1335,19 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
                } /* case PING_OPT_DEVICE */
                break;
 
+               case PING_OPT_MARK:
+               {
+#ifdef SO_MARK
+                       obj->mark     = *(int*)(value);
+                       obj->set_mark = 1;
+#else /* SO_MARK */
+                       ping_set_errno (obj, ENOTSUP);
+                       ret = -1;
+#endif /* !SO_MARK */
+
+               } /* case PING_OPT_MARK */
+               break;
+
                default:
                        ret = -2;
        } /* switch (option) */
@@ -1375,6 +1438,7 @@ int ping_send (pingobj_t *obj)
        if (fd4 != -1) num_fds++;
        if (fd6 != -1) num_fds++;
        max_fd = fd4 > fd6 ? fd4 : fd6;
+       assert (max_fd < FD_SETSIZE);
 
        while (pings > 0 || ptr != NULL)
        {
@@ -1416,7 +1480,8 @@ int ping_send (pingobj_t *obj)
                if ((status == -1) && (errno == EINTR))
                {
                        dprintf ("select was interrupted by signal..\n");
-                       continue;
+                       ping_set_errno (obj, EINTR);
+                       return (-1);
                }
                else if (status < 0)
                {
@@ -1444,7 +1509,7 @@ int ping_send (pingobj_t *obj)
                                if (!ping_receive_one(obj, &nowtime, AF_INET))
                                        --pings;
                        }
-                       else if (ptr != NULL && ptr->addrfamily == AF_INET && 
+                       else if (ptr != NULL && ptr->addrfamily == AF_INET &&
                                                FD_ISSET (fd4, &write_fds))
                        {
                                if (!ping_send_one(obj, ptr, fd4))
diff --git a/src/liboping.pc.in b/src/liboping.pc.in
new file mode 100644 (file)
index 0000000..5016623
--- /dev/null
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+includedir=@includedir@
+libdir=@libdir@
+
+Name: @PACKAGE_NAME@
+Description: C/C++ library to generate ICMP ECHO_REQUESTs
+URL: @PACKAGE_URL@
+Version: @PACKAGE_VERSION@
+Cflags: -I${includedir}
+Libs: -L${libdir} -loping
+Libs.private: @LIBOPING_PC_LIBS_PRIVATE@
index d2c42f9..c36a89c 100644 (file)
@@ -80,7 +80,7 @@ applicable.
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index e076299..f1e0807 100644 (file)
@@ -31,7 +31,7 @@ supports colors.
 
 =item B<-4>
 
-Force the use of IPv4. 
+Force the use of IPv4.
 
 =item B<-6>
 
@@ -46,6 +46,12 @@ Send (and receive) I<count> ICMP packets, then stop and exit.
 Send one ICMP packet (per host) each I<interval> seconds. This can be a
 floating-point number to specify sub-second precision.
 
+=item B<-w> I<timeout>
+
+Specifies the time to wait for an C<ECHO REPLY> packet before giving up, in
+seconds. This can be a floating point number for sub-second precision. Defaults
+to B<1.0> seconds.
+
 =item B<-t> I<ttl>
 
 Set the IP Time to Live to I<ttl>. This must be a number between (and
@@ -79,6 +85,12 @@ real user ID (as returned by L<getuid(2)>) and the effective user ID (as
 returned by L<geteuid(2)>) differ, the only argument allowed for this option is
 "-" (i.e. standard input).
 
+=item B<-O> I<filename>
+
+Write measurements in I<Comma Separated Values> (CSV) format to I<filename>.
+This option writes three columns per row: wall clock time in (fractional)
+seconds since epoch, hostname and the round trip time in milliseconds.
+
 =item B<-Q> I<qos>
 
 Specify the I<Quality of Service> (QoS) for outgoing packets. This is a
@@ -166,6 +178,131 @@ I<Explicit Congestion Notification> (ECN), even if the deprecated
 I<Type of Service> (ToS) aliases were used to specify the bits of outgoing
 packets.
 
+=item B<-m> I<mark>
+
+I<Linux only> Sets the I<mark> (an integer number) on outgoing packets. This
+can be used by L<iptables(8)> and other networking infrastructure for filtering
+and routing.
+
+=item B<-u>|B<-U>
+
+I<noping only> B<-u> forces UTF-8 output, B<-U> disables UTF-8 output. If
+neither is given, the codeset is automatically determined from the locale.
+
+=item B<-g> B<none>|B<prettyping>|B<boxplot>|B<histogram>
+
+I<noping only> Selects the graph to display.
+
+=over 4
+
+=item B<none>
+
+Do not show a graph.
+
+=item B<prettyping>
+
+Show a graph with time on the x-axis, the y-axis shows the round-trip time.
+This is the default graph.
+
+If your terminal supports unicode and colors, they are used to improve
+the precision of the data shown: a green box is drawn for round-trip times up
+to one third of the configured timeout, the height representing the RTT. Longer
+RTTs will start to fill the box yellow (with a green background) and then red
+(with a yellow background). Lost packages are drawn as a bold red explamation
+mark.
+
+=item B<boxplot>
+
+Show a I<box plot> where the x-axis, i.e. the width of the window, is the
+round-trip time. The entire width of the window it the ping interval, set with
+the B<-i> option.
+
+The box is sized so it contains 50% of the replies. The vertical line shows the
+median. The whiskers are sized to contain 95% of the replies -- 2.5% below the
+whiskers and 2.5% above.
+
+  |----------[#####|##########]--------------------------------------------|
+  ^          ^     ^          ^                                            ^
+ 2.5%       25%   50%        75%                                         97.5%
+
+=item B<histogram>
+
+Show a I<histrogram> of the round-trip times. The width of the window is taken
+as round-trip time from 0ms on the left to the I<interval> (the B<-i> option,
+default 1000ms) on the right.
+
+The height of the graph is scaled so that the most-used buckets vertically fills
+the line. The buckets are colored green up to and including the 80th
+percentile, yellow up to and including the 95th percentile and red for the
+remainder.
+
+=back
+
+=item B<-b>
+
+Audible bell. Print a ASCII BEL character (\a or 0x07) when a packet
+is received before the timeout occurs. This can be useful in order to
+monitory hosts' connectivity without looking physically at the
+console, for example to trace network cables (start audible beep,
+disconnect cable N: if beep stops, the cable was in use) or to tell
+when a host returns from a reboot.
+
+This relies on the terminal bell to be functional. To enable the
+terminal bell, use the following instructions.
+
+=over 4
+
+=item
+
+the visual bell is disabled in your terminal emulator, with the +vb
+commandline flag or the following in your .Xresources:
+
+ XTerm*visualBell: false
+
+=item
+
+the PC speaker module is loaded in your kernel:
+
+ modprobe pcspkr
+
+=item
+
+X11 has the terminal bell enabled:
+
+ xset b on; xset b 100
+
+=item
+
+and finally, if you are using PulseAudio, that the module-x11-bell
+module is loaded with a pre-loaded sample defined in your pulseaudio
+configuration:
+
+ load-sample-lazy x11-bell /usr/share/sounds/freedesktop/stereo/complete.oga
+ load-module module-x11-bell sample=x11-bell
+
+=back
+
+=item B<-P> I<percent>
+
+Configures the latency percentile to report. I<percent> must be a number
+between zero and 100, exclusively in both cases. In general, defaults to B<95>.
+If B<-c> is given and a number less than 20, this would be the same as the
+maximum. In this case the default is chosen so that it excludes the maximum,
+e.g. if B<-cE<nbsp>5> is given, the default is I<80>. The calculated percentile
+is based on the last 900 packets (15 minutes with the default interval).
+
+=item B<-Z> I<percent>
+
+If any hosts have a drop rate higher than I<percent>, where I<percent> is a
+number between zero and 100 inclusively, exit with a non-zero exit status.
+Since it is not possible to have a higher drop rate than 100%, passing this
+limit will effectively disable the feature (the default). Setting the option to
+zero means that the exit status will only be zero if I<all> replies for I<all>
+hosts have been received.
+
+The exit status will indicate the number of hosts with more than I<percent>
+packets lost, up to a number of 255 failing hosts.
+
 =back
 
 =head1 COLORS
@@ -175,22 +312,19 @@ If supported by the terminal, I<noping> will highlight the round-trip times
 the "expected" range, yellow marks moderately unusual times and times that
 differ a lot from the expected value are printed in red.
 
-The information used to categorize round-trip times is the I<average>
-round-trip time and the I<standard deviation>. RTTs that differ from the
-average by less than the standard deviation are considered to be "normal" and
-are printed in green. Times that differ from the average more than the standard
-deviation but less than twice the standard deviation are considered "moderately
-unusual" and are printed in yellow. Times differing more than twice the
-standard deviation from the average are considered to be "unusual" and are
+The information used to categorize round-trip times is the I<percentile>. RTTs
+in the 80th percentile are considered to be "normal" and are printed in green.
+RTTs within the 95th percentile are considered "moderately unusual" and are
+printed in yellow. RTTs above that are considered to be "unusual" and are
 printed in red.
 
 =head1 SEE ALSO
 
-L<ping(8)>, L<http://www.fping.com/>, L<liboping(3)>
+L<ping(8)>, L<http://fping.org/>, L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>ff at octo.itE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2010 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index da082c8..019538c 100644 (file)
@@ -32,7 +32,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index df9a015..23ab2a7 100644 (file)
@@ -24,7 +24,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 254ac35..aaa18d3 100644 (file)
@@ -46,7 +46,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 860f448..74d2858 100644 (file)
@@ -44,7 +44,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index f0585ab..82bc0ab 100644 (file)
@@ -39,7 +39,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 744a552..849e156 100644 (file)
@@ -129,7 +129,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 8237dae..8bf9bd8 100644 (file)
@@ -36,7 +36,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2009 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 582b355..f8c4ff6 100644 (file)
@@ -75,6 +75,12 @@ C<IP_TOS> (IPv4) or C<IPV6_TCLASS> (IPv6) option. It is the caller's
 responsibility to chose a valid bit combination. For details, read the L<ip(7)>
 and L<ipv6(7)> manual pages, as well as I<RFCE<nbsp>2474>.
 
+=item B<PING_OPT_MARK>
+
+Mark (as in netfilter) outgoing packets using the SO_MARK socket option. Takes
+an int* pointer as a value. Setting this requires CAP_NET_ADMIN under Linux.
+Fails with C<operation not supported> on platforms which don't have SO_MARK.
+
 =back
 
 The I<val> argument is a pointer to the new value. It must not be NULL. It is
@@ -92,7 +98,7 @@ L<liboping(3)>
 
 =head1 AUTHOR
 
-liboping is written by Florian octo Forster E<lt>octo at verplant.orgE<gt>.
-Its homepage can be found at L<http://verplant.org/liboping/>.
+liboping is written by Florian "octo" Forster E<lt>ff at octo.itE<gt>.
+Its homepage can be found at L<http://noping.cc/>.
 
-(c) 2005-2010 by Florian octo Forster.
+Copyright (c) 2005-2016 by Florian "octo" Forster.
index 79d7569..528c90d 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * Object oriented C module to send ICMP and ICMPv6 `echo's.
- * Copyright (C) 2006-2011  Florian octo Forster <ff at octo.it>
+ * Copyright (C) 2006-2016  Florian octo Forster <ff at octo.it>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <sys/types.h>
 #endif
 
+#include <locale.h>
+#include <langinfo.h>
+
 #if USE_NCURSES
 # define NCURSES_OPAQUE 1
-# include <ncurses.h>
+/* http://newsgroups.derkeiler.com/Archive/Rec/rec.games.roguelike.development/2010-09/msg00050.html */
+# define _X_OPEN_SOURCE_EXTENDED
+
+#if defined HAVE_NCURSESW_CURSES_H
+#  include <ncursesw/curses.h>
+#elif defined HAVE_NCURSESW_H
+#  include <ncursesw.h>
+#elif defined HAVE_NCURSES_CURSES_H
+#  include <ncurses/curses.h>
+#elif defined HAVE_NCURSES_H
+#  include <ncurses.h>
+#else
+#  error "SysV or X/Open-compatible Curses header file required"
+#endif
 
 # define OPING_GREEN 1
 # define OPING_YELLOW 2
 # define OPING_RED 3
+# define OPING_GREEN_HIST 4
+# define OPING_YELLOW_HIST 5
+# define OPING_RED_HIST 6
+
+double const threshold_green = 0.8;
+double const threshold_yellow = 0.95;
+
+static char const * const hist_symbols_utf8[] = {
+       "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█" };
+static size_t const hist_symbols_utf8_num = sizeof (hist_symbols_utf8)
+       / sizeof (hist_symbols_utf8[0]);
+
+/* scancodes for 6 levels of horizontal bars, ncurses-specific */
+/* those are not the usual constants because those are not constant */
+static int const hist_symbols_acs[] = {
+       115, /* ACS_S9 "⎽" */
+       114, /* ACS_S7 "⎼" */
+       113, /* ACS_S5 "─" */
+       112, /* ACS_S3 "⎻" */
+       111  /* ACS_S1 "⎺" */
+};
+static size_t const hist_symbols_acs_num = sizeof (hist_symbols_acs)
+       / sizeof (hist_symbols_acs[0]);
+
+/* use different colors without a background for scancodes */
+static int const hist_colors_utf8[] = {
+       OPING_GREEN_HIST, OPING_YELLOW_HIST, OPING_RED_HIST };
+static int const hist_colors_acs[] = {
+       OPING_GREEN, OPING_YELLOW, OPING_RED };
+/* assuming that both arrays are the same size */
+static size_t const hist_colors_num = sizeof (hist_colors_utf8)
+       / sizeof (hist_colors_utf8[0]);
 #endif
 
+/* "─" */
+#define BOXPLOT_WHISKER_BAR       (113 | A_ALTCHARSET)
+/* "├" */
+#define BOXPLOT_WHISKER_LEFT_END  (116 | A_ALTCHARSET)
+/* "┤" */
+#define BOXPLOT_WHISKER_RIGHT_END (117 | A_ALTCHARSET)
+/* Inverted */
+#define BOXPLOT_BOX               ' '
+/* "│", inverted */
+#define BOXPLOT_MEDIAN            (120 | A_ALTCHARSET)
+
 #include "oping.h"
 
 #ifndef _POSIX_SAVED_IDS
 # define _POSIX_SAVED_IDS 0
 #endif
 
+#ifndef IPTOS_MINCOST
+# define IPTOS_MINCOST 0x02
+#endif
+
 /* Remove GNU specific __attribute__ settings when using another compiler */
 #if !__GNUC__
 # define __attribute__(x) /**/
@@ -103,10 +166,31 @@ typedef struct ping_context
        int req_sent;
        int req_rcvd;
 
-       double latency_min;
-       double latency_max;
        double latency_total;
-       double latency_total_square;
+
+#ifndef HISTORY_SIZE_MAX
+# define HISTORY_SIZE_MAX 900
+#endif
+       /* The last n RTTs in the order they were sent. */
+       double history_by_time[HISTORY_SIZE_MAX];
+
+       /* Current number of entries in the history. This is a value between 0
+        * and HISTORY_SIZE_MAX. */
+       size_t history_size;
+
+       /* Total number of reponses received. */
+       size_t history_received;
+
+       /* Index of the next RTT to be written to history_by_time. This wraps
+        * around to 0 once the histroty has grown to HISTORY_SIZE_MAX. */
+       size_t history_index;
+
+       /* The last history_size RTTs sorted by value. timed out packets (NAN
+        * entries) are sorted to the back. */
+       double history_by_value[HISTORY_SIZE_MAX];
+
+       /* If set to true, history_by_value has to be re-calculated. */
+       _Bool history_dirty;
 
 #if USE_NCURSES
        WINDOW *window;
@@ -114,15 +198,27 @@ typedef struct ping_context
 } ping_context_t;
 
 static double  opt_interval   = 1.0;
+static double  opt_timeout    = PING_DEF_TIMEOUT;
 static int     opt_addrfamily = PING_DEF_AF;
 static char   *opt_srcaddr    = NULL;
 static char   *opt_device     = NULL;
+static char   *opt_mark       = NULL;
 static char   *opt_filename   = NULL;
 static int     opt_count      = -1;
 static int     opt_send_ttl   = 64;
 static uint8_t opt_send_qos   = 0;
+#define OPING_DEFAULT_PERCENTILE 95.0
+static double  opt_percentile = -1.0;
+static double  opt_exit_status_threshold = 1.0;
+#if USE_NCURSES
+static int     opt_show_graph = 1;
+static int     opt_utf8       = 0;
+#endif
+static char   *opt_outfile    = NULL;
+static int     opt_bell       = 0;
 
-static int host_num = 0;
+static int host_num  = 0;
+static FILE *outfile = NULL;
 
 #if USE_NCURSES
 static WINDOW *main_win = NULL;
@@ -136,25 +232,17 @@ static void sigint_handler (int signal) /* {{{ */
        opt_count = 0;
 } /* }}} void sigint_handler */
 
-static ping_context_t *context_create (void) /* {{{ */
+static ping_context_t *context_create () /* {{{ */
 {
-       ping_context_t *ret;
-
-       if ((ret = malloc (sizeof (ping_context_t))) == NULL)
+       ping_context_t *ctx = calloc (1, sizeof (*ctx));
+       if (ctx == NULL)
                return (NULL);
 
-       memset (ret, '\0', sizeof (ping_context_t));
-
-       ret->latency_min   = -1.0;
-       ret->latency_max   = -1.0;
-       ret->latency_total = 0.0;
-       ret->latency_total_square = 0.0;
-
 #if USE_NCURSES
-       ret->window = NULL;
+       ctx->window = NULL;
 #endif
 
-       return (ret);
+       return (ctx);
 } /* }}} ping_context_t *context_create */
 
 static void context_destroy (ping_context_t *context) /* {{{ */
@@ -173,37 +261,126 @@ static void context_destroy (ping_context_t *context) /* {{{ */
        free (context);
 } /* }}} void context_destroy */
 
-static double context_get_average (ping_context_t *ctx) /* {{{ */
+static int compare_double (void const *arg0, void const *arg1) /* {{{ */
 {
-       double num_total;
+       double dbl0 = *((double *) arg0);
+       double dbl1 = *((double *) arg1);
 
-       if (ctx == NULL)
-               return (-1.0);
+       if (isnan (dbl0))
+       {
+               if (isnan (dbl1))
+                       return 0;
+               else
+                       return 1;
+       }
+       else if (isnan (dbl1))
+               return -1;
+       else if (dbl0 < dbl1)
+               return -1;
+       else if (dbl0 > dbl1)
+               return 1;
+       else
+               return 0;
+} /* }}} int compare_double */
+
+static void clean_history (ping_context_t *ctx) /* {{{ */
+{
+       size_t i;
+
+       if (!ctx->history_dirty)
+               return;
+
+       /* Copy all values from by_time to by_value. */
+       memcpy (ctx->history_by_value, ctx->history_by_time,
+                       sizeof (ctx->history_by_time));
 
-       if (ctx->req_rcvd < 1)
-               return (-0.0);
+       /* Sort all RTTs. */
+       qsort (ctx->history_by_value, ctx->history_size, sizeof
+                       (ctx->history_by_value[0]), compare_double);
 
-       num_total = (double) ctx->req_rcvd;
-       return (ctx->latency_total / num_total);
-} /* }}} double context_get_average */
+       /* Update the number of received RTTs. */
+       ctx->history_received = 0;
+       for (i = 0; i < ctx->history_size; i++)
+               if (!isnan (ctx->history_by_value[i]))
+                       ctx->history_received++;
 
-static double context_get_stddev (ping_context_t *ctx) /* {{{ */
+       /* Mark as clean. */
+       ctx->history_dirty = 0;
+} /* }}} void clean_history */
+
+static double percentile_to_latency (ping_context_t *ctx, /* {{{ */
+               double percentile)
 {
-       double num_total;
+       size_t index;
 
-       if (ctx == NULL)
-               return (-1.0);
+       clean_history (ctx);
 
-       if (ctx->req_rcvd < 1)
-               return (-0.0);
-       else if (ctx->req_rcvd < 2)
-               return (0.0);
+       /* Not a single packet was received successfully. */
+       if (ctx->history_received == 0)
+               return NAN;
+
+       if (percentile <= 0.0)
+               index = 0;
+       else if (percentile >= 100.0)
+               index = ctx->history_received - 1;
+       else
+       {
+               index = (size_t) ceil ((percentile / 100.0) * ((double) ctx->history_received));
+               assert (index > 0);
+               index--;
+       }
 
-       num_total = (double) ctx->req_rcvd;
-       return (sqrt (((num_total * ctx->latency_total_square)
-                                       - (ctx->latency_total * ctx->latency_total))
-                               / (num_total * (num_total - 1.0))));
-} /* }}} double context_get_stddev */
+       return (ctx->history_by_value[index]);
+} /* }}} double percentile_to_latency */
+
+#if USE_NCURSES
+static double latency_to_ratio (ping_context_t *ctx, /* {{{ */
+               double latency)
+{
+       size_t low;
+       size_t high;
+       size_t index;
+
+       clean_history (ctx);
+
+       /* Not a single packet was received successfully. */
+       if (ctx->history_received == 0)
+               return NAN;
+
+       low = 0;
+       high = ctx->history_received - 1;
+
+       if (latency < ctx->history_by_value[low])
+               return 0.0;
+       else if (latency >= ctx->history_by_value[high])
+               return 100.0;
+
+       /* Do a binary search for the latency. This will work even when the
+        * exact latency is not in the array. If the latency is in the array
+        * multiple times, "low" will be set to the index of the last
+        * occurrence. The value at index "high" will be larger than the
+        * searched for latency (assured by the above "if" block. */
+       while ((high - low) > 1)
+       {
+               index = (high + low) / 2;
+
+               if (ctx->history_by_value[index] > latency)
+                       high = index;
+               else
+                       low = index;
+       }
+
+       assert (ctx->history_by_value[high] > latency);
+       assert (ctx->history_by_value[low] <= latency);
+
+       if (ctx->history_by_value[low] == latency)
+               index = low;
+       else
+               index = high;
+
+       return (((double) (index + 1)) / ((double) ctx->history_received));
+} /* }}} double latency_to_ratio */
+#endif
 
 static double context_get_packet_loss (const ping_context_t *ctx) /* {{{ */
 {
@@ -259,15 +436,25 @@ static void usage_exit (const char *name, int status) /* {{{ */
                        "  -4|-6        force the use of IPv4 or IPv6\n"
                        "  -c count     number of ICMP packets to send\n"
                        "  -i interval  interval with which to send ICMP packets\n"
+                       "  -w timeout   time to wait for replies, in seconds\n"
                        "  -t ttl       time to live for each ICMP packet\n"
                        "  -Q qos       Quality of Service (QoS) of outgoing packets\n"
                        "               Use \"-Q help\" for a list of valid options.\n"
                        "  -I srcaddr   source address\n"
                        "  -D device    outgoing interface name\n"
-                       "  -f filename  filename to read hosts from\n"
+                       "  -m mark      mark to set on outgoing packets\n"
+                       "  -f filename  read hosts from <filename>\n"
+                       "  -O filename  write RTT measurements to <filename>\n"
+#if USE_NCURSES
+                       "  -u / -U      force / disable UTF-8 output\n"
+                       "  -g graph     graph type to draw\n"
+#endif
+                       "  -P percent   Report the n'th percentile of latency\n"
+                       "  -Z percent   Exit with non-zero exit status if more than this percentage of\n"
+                       "               probes timed out. (default: never)\n"
 
-                       "\noping "PACKAGE_VERSION", http://verplant.org/liboping/\n"
-                       "by Florian octo Forster <octo@verplant.org>\n"
+                       "\noping "PACKAGE_VERSION", http://noping.cc/\n"
+                       "by Florian octo Forster <ff@octo.it>\n"
                        "for contributions see `AUTHORS'\n",
                        name);
        exit (status);
@@ -467,7 +654,11 @@ static int read_options (int argc, char **argv) /* {{{ */
 
        while (1)
        {
-               optchar = getopt (argc, argv, "46c:hi:I:t:Q:f:D:");
+               optchar = getopt (argc, argv, "46c:hi:I:t:Q:f:D:Z:O:P:m:w:b"
+#if USE_NCURSES
+                               "uUg:"
+#endif
+                               );
 
                if (optchar == -1)
                        break;
@@ -484,7 +675,12 @@ static int read_options (int argc, char **argv) /* {{{ */
                                        int new_count;
                                        new_count = atoi (optarg);
                                        if (new_count > 0)
+                                       {
                                                opt_count = new_count;
+
+                                               if ((opt_percentile < 0.0) && (opt_count < 20))
+                                                       opt_percentile = 100.0 * (opt_count - 1) / opt_count;
+                                       }
                                        else
                                                fprintf(stderr, "Ignoring invalid count: %s\n",
                                                                optarg);
@@ -510,6 +706,19 @@ static int read_options (int argc, char **argv) /* {{{ */
                                                opt_interval = new_interval;
                                }
                                break;
+
+                       case 'w':
+                               {
+                                       char *endp = NULL;
+                                       double t = strtod (optarg, &endp);
+                                       if ((optarg[0] != 0) && (endp != NULL) && (*endp == 0))
+                                               opt_timeout = t;
+                                       else
+                                               fprintf (stderr, "Ignoring invalid timeout: %s\n",
+                                                               optarg);
+                               }
+                               break;
+
                        case 'I':
                                {
                                        if (opt_srcaddr != NULL)
@@ -522,6 +731,10 @@ static int read_options (int argc, char **argv) /* {{{ */
                                opt_device = optarg;
                                break;
 
+                       case 'm':
+                               opt_mark = optarg;
+                               break;
+
                        case 't':
                        {
                                int new_send_ttl;
@@ -538,14 +751,82 @@ static int read_options (int argc, char **argv) /* {{{ */
                                set_opt_send_qos (optarg);
                                break;
 
+                       case 'O':
+                               {
+                                       free (opt_outfile);
+                                       opt_outfile = strdup (optarg);
+                               }
+                               break;
+
+                       case 'P':
+                               {
+                                       double new_percentile;
+                                       new_percentile = atof (optarg);
+                                       if (isnan (new_percentile)
+                                                       || (new_percentile < 0.1)
+                                                       || (new_percentile > 100.0))
+                                               fprintf (stderr, "Ignoring invalid percentile: %s\n",
+                                                               optarg);
+                                       else
+                                               opt_percentile = new_percentile;
+                               }
+                               break;
+
+#if USE_NCURSES
+                       case 'g':
+                               if (strcasecmp ("none", optarg) == 0)
+                                       opt_show_graph = 0;
+                               else if (strcasecmp ("prettyping", optarg) == 0)
+                                       opt_show_graph = 1;
+                               else if (strcasecmp ("histogram", optarg) == 0)
+                                       opt_show_graph = 2;
+                               else if (strcasecmp ("boxplot", optarg) == 0)
+                                       opt_show_graph = 3;
+                               else
+                                       fprintf (stderr, "Unknown graph option: %s\n", optarg);
+                               break;
+
+                       case 'u':
+                               opt_utf8 = 2;
+                               break;
+                       case 'U':
+                               opt_utf8 = 1;
+                               break;
+#endif
+                       case 'b':
+                               opt_bell = 1;
+                               break;
+
+                       case 'Z':
+                       {
+                               char *endptr = NULL;
+                               double tmp;
+
+                               errno = 0;
+                               tmp = strtod (optarg, &endptr);
+                               if ((errno != 0) || (endptr == NULL) || (*endptr != 0) || (tmp < 0.0) || (tmp > 100.0))
+                               {
+                                       fprintf (stderr, "Ignoring invalid -Z argument: %s\n", optarg);
+                                       fprintf (stderr, "The \"-Z\" option requires a numeric argument between 0 and 100.\n");
+                               }
+                               else
+                                       opt_exit_status_threshold = tmp / 100.0;
+
+                               break;
+                       }
+
                        case 'h':
                                usage_exit (argv[0], 0);
                                break;
+
                        default:
                                usage_exit (argv[0], 1);
                }
        }
 
+       if (opt_percentile <= 0.0)
+               opt_percentile = OPING_DEFAULT_PERCENTILE;
+
        return (optind);
 } /* }}} read_options */
 
@@ -597,12 +878,356 @@ static void time_calc (struct timespec *ts_dest, /* {{{ */
 } /* }}} void time_calc */
 
 #if USE_NCURSES
-static int update_stats_from_context (ping_context_t *ctx) /* {{{ */
+static _Bool has_utf8() /* {{{ */
 {
+# if HAVE_NCURSESW_NCURSES_H
+       if (!opt_utf8)
+       {
+               /* Automatically determine */
+               if (strcasecmp ("UTF-8", nl_langinfo (CODESET)) == 0)
+                       opt_utf8 = 2;
+               else
+                       opt_utf8 = 1;
+       }
+       return ((_Bool) (opt_utf8 - 1));
+# else
+       return (0);
+# endif
+} /* }}} _Bool has_utf8 */
+
+static int update_graph_boxplot (ping_context_t *ctx) /* {{{ */
+{
+       uint32_t *counters;
+       double *ratios;
+       size_t i;
+       size_t x_max;
+       size_t x;
+
+       clean_history (ctx);
+
+       if (ctx->history_received == 0)
+               return (ENOENT);
+
+       x_max = (size_t) getmaxx (ctx->window);
+       if (x_max <= 8)
+               return (EINVAL);
+       x_max -= 4;
+
+       counters = calloc (x_max, sizeof (*counters));
+       ratios = calloc (x_max, sizeof (*ratios));
+
+       /* Bucketize */
+       for (i = 0; i < ctx->history_received; i++)
+       {
+               double latency = ctx->history_by_value[i] / 1000.0;
+               size_t index = (size_t) (((double) x_max) * latency / opt_interval);
+
+               if (index >= x_max)
+                       index = x_max - 1;
+
+               counters[index]++;
+       }
+
+       /* Sum and calc ratios */
+       ratios[0] = ((double) counters[0]) / ((double) ctx->history_received);
+       for (x = 1; x < x_max; x++)
+       {
+               counters[x] += counters[x - 1];
+               ratios[x] = ((double) counters[x]) / ((double) ctx->history_received);
+       }
+
+       for (x = 0; x < x_max; x++)
+       {
+               int symbol = ' ';
+               _Bool reverse = 0;
+
+               if (x == 0)
+               {
+                       if (ratios[x] >= 0.5)
+                       {
+                               symbol = BOXPLOT_MEDIAN;
+                               reverse = 1;
+                       }
+                       else if (ratios[x] > 0.25)
+                       {
+                               symbol = BOXPLOT_BOX;
+                               reverse = 1;
+                       }
+                       else if (ratios[x] > 0.025)
+                               symbol = BOXPLOT_WHISKER_BAR;
+                       else
+                               symbol = ' '; /* NOP */
+               }
+               else /* (x != 0) */
+               {
+                       if ((ratios[x - 1] < 0.5) && (ratios[x] >= 0.5))
+                       {
+                               symbol = BOXPLOT_MEDIAN;
+                               reverse = 1;
+                       }
+                       else if (((ratios[x] >= 0.25) && (ratios[x] <= 0.75))
+                                       || ((ratios[x - 1] < 0.75) && (ratios[x] > 0.75)))
+                       {
+                               symbol = BOXPLOT_BOX;
+                               reverse = 1;
+                       }
+                       else if ((ratios[x] < 0.5) && (ratios[x] >= 0.025))
+                       {
+                               if (ratios[x - 1] < 0.025)
+                                       symbol = BOXPLOT_WHISKER_LEFT_END;
+                               else
+                                       symbol = BOXPLOT_WHISKER_BAR;
+                       }
+                       else if ((ratios[x] > .5) && (ratios[x] < 0.975))
+                       {
+                               symbol = BOXPLOT_WHISKER_BAR;
+                       }
+                       else if ((ratios[x] >= 0.975) && (ratios[x - 1] < 0.975))
+                               symbol = BOXPLOT_WHISKER_RIGHT_END;
+               }
+
+               if (reverse)
+                       wattron (ctx->window, A_REVERSE);
+               mvwaddch (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
+               // mvwprintw (ctx->window, /* y = */ 3, /* x = */ (int) (x + 2), symbol);
+               if (reverse)
+                       wattroff (ctx->window, A_REVERSE);
+       }
+
+       free (counters);
+       free (ratios);
+       return (0);
+} /* }}} int update_graph_boxplot */
+
+static int update_graph_prettyping (ping_context_t *ctx, /* {{{ */
+               double latency, unsigned int sequence)
+{
+       size_t x;
+       size_t x_max;
+       size_t history_offset;
+
+       x_max = (size_t) getmaxx (ctx->window);
+       if (x_max <= 4)
+               return (EINVAL);
+       x_max -= 4;
+
+       /* Determine the first index in the history we need to draw
+        * the graph. */
+       history_offset = 0;
+       if (((size_t) x_max) < ctx->history_size) /* window is smaller than history */
+       {
+               if (ctx->history_index > x_max)
+                       history_offset = ctx->history_index - x_max;
+               else /* wrap around */
+                       history_offset = ctx->history_index + ctx->history_size - x_max;
+       }
+       else /* window is larger than history */
+       {
+               if (ctx->history_index != ctx->history_size) /* no longer growing. */
+                       history_offset = ctx->history_index;
+               else /* start-up */
+                       history_offset = 0;
+       }
+
+       for (x = 0; x < x_max; x++)
+       {
+               size_t index;
+               double latency;
+
+               int color = OPING_RED;
+               char const *symbol = "!";
+               int symbolc = '!';
+
+               if (x >= ctx->history_size)
+               {
+                       mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, ' ');
+                       continue;
+               }
+
+               index = (history_offset + x) % ctx->history_size;
+               latency = ctx->history_by_time[index];
+
+               if (latency >= 0.0)
+               {
+                       double ratio;
+
+                       size_t symbols_num = hist_symbols_acs_num;
+                       size_t colors_num = 1;
+
+                       size_t index_symbols;
+                       size_t index_colors;
+                       size_t intensity;
+
+                       /* latency is in milliseconds, opt_interval is in seconds. */
+                       ratio = (latency * 0.001) / opt_interval;
+                       if (ratio > 1) {
+                               ratio = 1.0;
+                       }
+
+                       if (has_utf8 ())
+                               symbols_num = hist_symbols_utf8_num;
+
+                       if (has_colors () == TRUE)
+                               colors_num = hist_colors_num;
+
+                       intensity = (size_t) (ratio * ((double) (symbols_num * colors_num)));
+                       if (intensity >= (symbols_num * colors_num))
+                               intensity = (symbols_num * colors_num) - 1;
+
+                       index_symbols = intensity % symbols_num;
+                       assert (index_symbols < symbols_num);
+
+                       index_colors = intensity / symbols_num;
+                       assert (index_colors < colors_num);
+
+                       if (has_utf8())
+                       {
+                               color = hist_colors_utf8[index_colors];
+                               symbol = hist_symbols_utf8[index_symbols];
+                       }
+                       else
+                       {
+                               color = hist_colors_acs[index_colors];
+                               symbolc = hist_symbols_acs[index_symbols] | A_ALTCHARSET;
+                       }
+               }
+               else /* if (!(latency >= 0.0)) */
+                       wattron (ctx->window, A_BOLD);
+
+               if (has_colors () == TRUE)
+                       wattron (ctx->window, COLOR_PAIR(color));
+
+               if (has_utf8())
+                       mvwprintw (ctx->window, /* y = */ 3, /* x = */ x + 2, symbol);
+               else
+                       mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, symbolc);
+
+               if (has_colors () == TRUE)
+                       wattroff (ctx->window, COLOR_PAIR(color));
+
+               /* Use negation here to handle NaN correctly. */
+               if (!(latency >= 0.0))
+                       wattroff (ctx->window, A_BOLD);
+       } /* for (x) */
+
+       return (0);
+} /* }}} int update_graph_prettyping */
+
+static int update_graph_histogram (ping_context_t *ctx) /* {{{ */
+{
+       uint32_t *counters;
+       uint32_t *accumulated;
+       uint32_t max;
+       size_t i;
+       size_t x_max;
+       size_t x;
+
+       size_t symbols_num = hist_symbols_acs_num;
+
+       clean_history (ctx);
+
+       if (ctx->history_received == 0)
+               return (ENOENT);
+
+       if (has_utf8 ())
+               symbols_num = hist_symbols_utf8_num;
+
+       x_max = (size_t) getmaxx (ctx->window);
+       if (x_max <= 4)
+               return (EINVAL);
+       x_max -= 4;
+
+       counters = calloc (x_max, sizeof (*counters));
+       accumulated = calloc (x_max, sizeof (*accumulated));
+
+       /* Bucketize */
+       max = 0;
+       for (i = 0; i < ctx->history_received; i++)
+       {
+               double latency = ctx->history_by_value[i] / 1000.0;
+               size_t index = (size_t) (((double) x_max) * latency / opt_interval);
+
+               if (index >= x_max)
+                       index = x_max - 1;
+
+               counters[index]++;
+               if (max < counters[index])
+                       max = counters[index];
+       }
+
+       /* Sum */
+       accumulated[0] = counters[0];
+       for (x = 1; x < x_max; x++)
+               accumulated[x] = counters[x] + accumulated[x - 1];
+
+       /* Calculate ratios */
+       for (x = 0; x < x_max; x++)
+       {
+               double height = ((double) counters[x]) / ((double) max);
+               double ratio_this = ((double) accumulated[x]) / ((double) ctx->history_received);
+               double ratio_prev = 0.0;
+               size_t index;
+               int color = 0;
+
+               index = (size_t) (height * ((double) symbols_num));
+               if (index >= symbols_num)
+                       index = symbols_num - 1;
+
+               if (x > 0)
+                       ratio_prev = ((double) accumulated[x - 1]) / ((double) ctx->history_received);
+
+               if (has_colors () == TRUE)
+               {
+                       if ((ratio_this <= threshold_green)
+                                       || ((ratio_prev < threshold_green)
+                                               && (ratio_this > threshold_green)))
+                               color = OPING_GREEN;
+                       else if ((ratio_this <= threshold_yellow)
+                                       || ((ratio_prev < threshold_yellow)
+                                               && (ratio_this > threshold_yellow)))
+                               color = OPING_YELLOW;
+                       else
+                               color = OPING_RED;
+
+                       wattron (ctx->window, COLOR_PAIR(color));
+               }
+
+               if (counters[x] == 0)
+                       mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2, ' ');
+               else if (has_utf8 ())
+                       mvwprintw (ctx->window, /* y = */ 3, /* x = */ x + 2,
+                                       hist_symbols_utf8[index]);
+               else
+                       mvwaddch (ctx->window, /* y = */ 3, /* x = */ x + 2,
+                                       hist_symbols_acs[index] | A_ALTCHARSET);
+
+               if (has_colors () == TRUE)
+                       wattroff (ctx->window, COLOR_PAIR(color));
+
+       }
+
+       free (accumulated);
+       return (0);
+} /* }}} int update_graph_histogram */
+
+static int update_stats_from_context (ping_context_t *ctx, pingobj_iter_t *iter) /* {{{ */
+{
+       double latency = -1.0;
+       size_t buffer_len = sizeof (latency);
+
+       ping_iterator_get_info (iter, PING_INFO_LATENCY,
+                       &latency, &buffer_len);
+
+       unsigned int sequence = 0;
+       buffer_len = sizeof (sequence);
+       ping_iterator_get_info (iter, PING_INFO_SEQUENCE,
+                       &sequence, &buffer_len);
+
+
        if ((ctx == NULL) || (ctx->window == NULL))
                return (EINVAL);
 
-       werase (ctx->window);
+       /* werase (ctx->window); */
 
        box (ctx->window, 0, 0);
        wattron (ctx->window, A_BOLD);
@@ -618,20 +1243,28 @@ static int update_stats_from_context (ping_context_t *ctx) /* {{{ */
                        ctx->latency_total);
        if (ctx->req_rcvd != 0)
        {
-               double average;
-               double deviation;
+               double min;
+               double median;
+               double max;
+               double percentile;
+
+               min = percentile_to_latency (ctx, 0.0);
+               median = percentile_to_latency (ctx, 50.0);
+               max = percentile_to_latency (ctx, 100.0);
+               percentile = percentile_to_latency (ctx, opt_percentile);
 
-               average = context_get_average (ctx);
-               deviation = context_get_stddev (ctx);
-                       
                mvwprintw (ctx->window, /* y = */ 2, /* x = */ 2,
-                               "rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms",
-                               ctx->latency_min,
-                               average,
-                               ctx->latency_max,
-                               deviation);
+                               "RTT[ms]: min = %.0f, median = %.0f, p(%.0f) = %.0f, max = %.0f  ",
+                               min, median, opt_percentile, percentile, max);
        }
 
+       if (opt_show_graph == 1)
+               update_graph_prettyping (ctx, latency, sequence);
+       else if (opt_show_graph == 2)
+               update_graph_histogram (ctx);
+       else if (opt_show_graph == 3)
+               update_graph_boxplot (ctx);
+
        wrefresh (ctx->window);
 
        return (0);
@@ -643,12 +1276,13 @@ static int on_resize (pingobj_t *ping) /* {{{ */
        int width = 0;
        int height = 0;
        int main_win_height;
+       int box_height = (opt_show_graph == 0) ? 4 : 5;
 
        getmaxyx (stdscr, height, width);
        if ((height < 1) || (width < 1))
                return (EINVAL);
 
-       main_win_height = height - (4 * host_num);
+       main_win_height = height - (box_height * host_num);
        wresize (main_win, main_win_height, /* width = */ width);
        /* Allow scrolling */
        scrollok (main_win, TRUE);
@@ -672,9 +1306,9 @@ static int on_resize (pingobj_t *ping) /* {{{ */
                        delwin (context->window);
                        context->window = NULL;
                }
-               context->window = newwin (/* height = */ 4,
+               context->window = newwin (/* height = */ box_height,
                                /* width = */ width,
-                               /* y = */ main_win_height + (4 * context->index),
+                               /* y = */ main_win_height + (box_height * context->index),
                                /* x = */ 0);
        }
 
@@ -692,6 +1326,13 @@ static int check_resize (pingobj_t *ping) /* {{{ */
                        break;
                else if (key == KEY_RESIZE)
                        need_resize = 1;
+               else if (key == 'g')
+               {
+                       if (opt_show_graph == 3)
+                               opt_show_graph = 1;
+                       else if (opt_show_graph > 0)
+                               opt_show_graph++;
+               }
        }
 
        if (need_resize)
@@ -706,6 +1347,7 @@ static int pre_loop_hook (pingobj_t *ping) /* {{{ */
        int width = 0;
        int height = 0;
        int main_win_height;
+       int box_height = (opt_show_graph == 0) ? 4 : 5;
 
        initscr ();
        cbreak ();
@@ -719,12 +1361,16 @@ static int pre_loop_hook (pingobj_t *ping) /* {{{ */
        if (has_colors () == TRUE)
        {
                start_color ();
-               init_pair (OPING_GREEN,  COLOR_GREEN,  /* default = */ 0);
-               init_pair (OPING_YELLOW, COLOR_YELLOW, /* default = */ 0);
-               init_pair (OPING_RED,    COLOR_RED,    /* default = */ 0);
+               use_default_colors ();
+               init_pair (OPING_GREEN,  COLOR_GREEN,  /* default = */ -1);
+               init_pair (OPING_YELLOW, COLOR_YELLOW, /* default = */ -1);
+               init_pair (OPING_RED,    COLOR_RED,    /* default = */ -1);
+               init_pair (OPING_GREEN_HIST,  COLOR_GREEN,  -1);
+               init_pair (OPING_YELLOW_HIST, COLOR_YELLOW, COLOR_GREEN);
+               init_pair (OPING_RED_HIST,    COLOR_RED,    COLOR_YELLOW);
        }
 
-       main_win_height = height - (4 * host_num);
+       main_win_height = height - (box_height * host_num);
        main_win = newwin (/* height = */ main_win_height,
                        /* width = */ width,
                        /* y = */ 0, /* x = */ 0);
@@ -751,9 +1397,9 @@ static int pre_loop_hook (pingobj_t *ping) /* {{{ */
                        delwin (context->window);
                        context->window = NULL;
                }
-               context->window = newwin (/* height = */ 4,
+               context->window = newwin (/* height = */ box_height,
                                /* width = */ width,
-                               /* y = */ main_win_height + (4 * context->index),
+                               /* y = */ main_win_height + (box_height * context->index),
                                /* x = */ 0);
        }
 
@@ -819,6 +1465,30 @@ static int post_sleep_hook (__attribute__((unused)) pingobj_t *ping) /* {{{ */
 } /* }}} int post_sleep_hook */
 #endif
 
+static void update_context (ping_context_t *ctx, double latency) /* {{{ */
+{
+       ctx->req_sent++;
+
+       if (latency > 0.0)
+       {
+               ctx->req_rcvd++;
+               ctx->latency_total += latency;
+       }
+       else
+       {
+               latency = NAN;
+       }
+
+       ctx->history_by_time[ctx->history_index] = latency;
+
+       ctx->history_dirty = 1;
+
+       /* Update index and size. */
+       ctx->history_index = (ctx->history_index + 1) % HISTORY_SIZE_MAX;
+       if (ctx->history_size < HISTORY_SIZE_MAX)
+               ctx->history_size++;
+} /* }}} void update_context */
+
 static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
                __attribute__((unused)) int index)
 {
@@ -863,31 +1533,23 @@ static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
 # define HOST_PRINTF(...) printf(__VA_ARGS__)
 #endif
 
-       context->req_sent++;
+       update_context (context, latency);
+
        if (latency > 0.0)
        {
-               context->req_rcvd++;
-               context->latency_total += latency;
-               context->latency_total_square += (latency * latency);
-
-               if ((context->latency_max < 0.0) || (context->latency_max < latency))
-                       context->latency_max = latency;
-               if ((context->latency_min < 0.0) || (context->latency_min > latency))
-                       context->latency_min = latency;
-
 #if USE_NCURSES
                if (has_colors () == TRUE)
                {
+                       double ratio;
                        int color = OPING_GREEN;
-                       double average = context_get_average (context);
-                       double stddev = context_get_stddev (context);
 
-                       if ((latency < (average - (2 * stddev)))
-                                       || (latency > (average + (2 * stddev))))
-                               color = OPING_RED;
-                       else if ((latency < (average - stddev))
-                                       || (latency > (average + stddev)))
+                       ratio = latency_to_ratio (context, latency);
+                       if (ratio < threshold_green)
+                               color = OPING_GREEN;
+                       else if (ratio < threshold_yellow)
                                color = OPING_YELLOW;
+                       else
+                               color = OPING_RED;
 
                        HOST_PRINTF ("%zu bytes from %s (%s): icmp_seq=%u ttl=%i ",
                                        data_len, context->host, context->addr,
@@ -920,8 +1582,15 @@ static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
 #if USE_NCURSES
                }
 #endif
+                if (opt_bell) {
+#if USE_NCURSES
+                       beep();
+#else
+                       HOST_PRINTF ("\a");
+#endif
+                }
        }
-       else
+       else /* if (!(latency > 0.0)) */
        {
 #if USE_NCURSES
                if (has_colors () == TRUE)
@@ -945,15 +1614,33 @@ static void update_host_hook (pingobj_iter_t *iter, /* {{{ */
 #endif
        }
 
+       if (outfile != NULL)
+       {
+               struct timeval tv = {0};
+               if (gettimeofday (&tv, NULL) == 0)
+               {
+                       double t = ((double) tv.tv_sec) + (((double) tv.tv_usec) / 1000000.0);
+
+                       if ((sequence % 32) == 0)
+                               fprintf (outfile, "#time,host,latency[ms]\n");
+
+                       fprintf (outfile, "%.3f,\"%s\",%.2f\n", t, context->host, latency);
+               }
+       }
+
 #if USE_NCURSES
-       update_stats_from_context (context);
+       update_stats_from_context (context, iter);
        wrefresh (main_win);
 #endif
 } /* }}} void update_host_hook */
 
+/* Prints statistics for each host, cleans up the contexts and returns the
+ * number of hosts which failed to return more than the fraction
+ * opt_exit_status_threshold of pings. */
 static int post_loop_hook (pingobj_t *ping) /* {{{ */
 {
        pingobj_iter_t *iter;
+       int failure_count = 0;
 
 #if USE_NCURSES
        endwin ();
@@ -973,26 +1660,34 @@ static int post_loop_hook (pingobj_t *ping) /* {{{ */
                                context_get_packet_loss (context),
                                context->latency_total);
 
-               if (context->req_rcvd != 0)
                {
-                       double average;
-                       double deviation;
-
-                       average = context_get_average (context);
-                       deviation = context_get_stddev (context);
+                       double pct_failed = 1.0 - (((double) context->req_rcvd)
+                                       / ((double) context->req_sent));
+                       if (pct_failed > opt_exit_status_threshold)
+                               failure_count++;
+               }
 
-                       printf ("rtt min/avg/max/sdev = %.3f/%.3f/%.3f/%.3f ms\n",
-                                       context->latency_min,
-                                       average,
-                                       context->latency_max,
-                                       deviation);
+               if (context->req_rcvd != 0)
+               {
+                       double min;
+                       double median;
+                       double max;
+                       double percentile;
+
+                       min = percentile_to_latency (context, 0.0);
+                       median = percentile_to_latency (context, 50.0);
+                       max = percentile_to_latency (context, 100.0);
+                       percentile = percentile_to_latency (context, opt_percentile);
+
+                       printf ("RTT[ms]: min = %.0f, median = %.0f, p(%.0f) = %.0f, max = %.0f\n",
+                                       min, median, opt_percentile, percentile, max);
                }
 
                ping_iterator_set_context (iter, NULL);
                context_destroy (context);
        }
 
-       return (0);
+       return (failure_count);
 } /* }}} int post_loop_hook */
 
 int main (int argc, char **argv) /* {{{ */
@@ -1026,6 +1721,7 @@ int main (int argc, char **argv) /* {{{ */
        }
 #endif
 
+       setlocale(LC_ALL, "");
        optind = read_options (argc, argv);
 
 #if !_POSIX_SAVED_IDS
@@ -1076,6 +1772,12 @@ int main (int argc, char **argv) /* {{{ */
                /* printf ("ts_int = %i.%09li\n", (int) ts_int.tv_sec, ts_int.tv_nsec); */
        }
 
+       if (ping_setopt (ping, PING_OPT_TIMEOUT, (void*)(&opt_timeout)) != 0)
+       {
+               fprintf (stderr, "Setting timeout failed: %s\n",
+                               ping_get_error (ping));
+       }
+
        if (opt_addrfamily != PING_DEF_AF)
                ping_setopt (ping, PING_OPT_AF, (void *) &opt_addrfamily);
 
@@ -1097,6 +1799,24 @@ int main (int argc, char **argv) /* {{{ */
                }
        }
 
+       if (opt_mark != NULL)
+       {
+               char *endp = NULL;
+               int mark = (int) strtol (opt_mark, &endp, /* base = */ 0);
+               if ((opt_mark[0] != 0) && (endp != NULL) && (*endp == 0))
+               {
+                       if (ping_setopt(ping, PING_OPT_MARK, (void*)(&mark)) != 0)
+                       {
+                               fprintf (stderr, "Setting mark failed: %s\n",
+                                       ping_get_error (ping));
+                       }
+               }
+               else
+               {
+                       fprintf(stderr, "Ignoring invalid mark: %s\n", optarg);
+               }
+       }
+
        if (opt_filename != NULL)
        {
                FILE *infile;
@@ -1200,10 +1920,24 @@ int main (int argc, char **argv) /* {{{ */
                exit (EXIT_FAILURE);
        }
 
+       if (host_num == 0)
+               exit (EXIT_FAILURE);
+
 #if _POSIX_SAVED_IDS
        saved_set_uid = (uid_t) -1;
 #endif
 
+       if (opt_outfile != NULL)
+       {
+               outfile = fopen (opt_outfile, "a");
+               if (outfile == NULL)
+               {
+                       fprintf (stderr, "opening \"%s\" failed: %s\n",
+                                opt_outfile, strerror (errno));
+                       exit (EXIT_FAILURE);
+               }
+       }
+
        ping_initialize_contexts (ping);
 
        if (i == 0)
@@ -1230,7 +1964,12 @@ int main (int argc, char **argv) /* {{{ */
                        return (1);
                }
 
-               if (ping_send (ping) < 0)
+               status = ping_send (ping);
+               if (status == -EINTR)
+               {
+                       continue;
+               }
+               else if (status < 0)
                {
                        fprintf (stderr, "ping_send failed: %s\n",
                                        ping_get_error (ping));
@@ -1263,14 +2002,13 @@ int main (int argc, char **argv) /* {{{ */
                /* printf ("Sleeping for %i.%09li seconds\n", (int) ts_wait.tv_sec, ts_wait.tv_nsec); */
                while ((status = nanosleep (&ts_wait, &ts_wait)) != 0)
                {
-                       if (errno != EINTR)
+                       if (errno == EINTR)
                        {
-                               perror ("nanosleep");
-                               break;
+                               continue;
                        }
-                       else if (opt_count == 0)
+                       else
                        {
-                               /* sigint */
+                               perror ("nanosleep");
                                break;
                        }
                }
@@ -1281,11 +2019,25 @@ int main (int argc, char **argv) /* {{{ */
                        opt_count--;
        } /* while (opt_count != 0) */
 
-       post_loop_hook (ping);
+       /* Returns the number of failed hosts according to -Z. */
+       status = post_loop_hook (ping);
 
        ping_destroy (ping);
 
-       return (0);
+       if (outfile != NULL)
+       {
+               fclose (outfile);
+               outfile = NULL;
+       }
+
+       if (status == 0)
+               exit (EXIT_SUCCESS);
+       else
+       {
+               if (status > 255)
+                       status = 255;
+               exit (status);
+       }
 } /* }}} int main */
 
 /* vim: set fdm=marker : */
index e53d139..1970ef2 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * Object oriented C module to send ICMP and ICMPv6 `echo's.
- * Copyright (C) 2006-2011  Florian octo Forster <ff at octo.it>
+ * Copyright (C) 2006-2016  Florian octo Forster <ff at octo.it>
  *
  * This library is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by the
@@ -33,7 +33,7 @@
 extern "C" {
 #endif
 
-#define OPING_VERSION 1006001
+#define OPING_VERSION 1009000
 
 /*
  * Type definitions
@@ -53,6 +53,7 @@ typedef struct pingobj pingobj_t;
 #define PING_OPT_SOURCE  0x10
 #define PING_OPT_DEVICE  0x20
 #define PING_OPT_QOS     0x40
+#define PING_OPT_MARK    0x80
 
 #define PING_DEF_TIMEOUT 1.0
 #define PING_DEF_TTL     255