From: Florian Forster Date: Fri, 5 May 2017 06:43:52 +0000 (+0200) Subject: Merge remote-tracking branch 'github/master' into lh/performance X-Git-Url: https://git.octo.it/?p=liboping.git;a=commitdiff_plain;h=40f1574f0b5f713e44c73115553977900c9f933f;hp=d717d41928fab9354e88d91ef1996151565012d8 Merge remote-tracking branch 'github/master' into lh/performance --- diff --git a/.gitignore b/.gitignore index 53ca1f5..b9cb926 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/ChangeLog b/ChangeLog index e895bbe..7d85b14 100644 --- 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 diff --git a/Makefile.am b/Makefile.am index af22243..6993205 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 --- 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 index 0000000..6a5d62d --- /dev/null +++ b/autogen.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +autoreconf --warnings=all --install +echo "autoconfiguration done, to build: ./configure ; make" diff --git a/bindings/perl/Makefile.PL b/bindings/perl/Makefile.PL index 5525dd0..98f93d8 100644 --- a/bindings/perl/Makefile.PL +++ b/bindings/perl/Makefile.PL @@ -123,7 +123,7 @@ WriteMakefile( PREREQ_PM => {}, ($] >= 5.005 ? (ABSTRACT_FROM => 'lib/Net/Oping.pm', - AUTHOR => 'Florian Forster ') + AUTHOR => 'Florian Forster ') : ()), ($OPING_DEPEND ? (depend => $OPING_DEPEND) : ()), LIBS => [$OPING_LIBS], diff --git a/bindings/perl/Oping.xs b/bindings/perl/Oping.xs index 5731d3d..c88e3dc 100644 --- a/bindings/perl/Oping.xs +++ b/bindings/perl/Oping.xs @@ -19,7 +19,7 @@ * * Authors: * Olivier Fredj - * Florian octo Forster + * Florian octo Forster */ #include "EXTERN.h" #include "perl.h" diff --git a/bindings/perl/README b/bindings/perl/README index 4d51a96..040a38a 100644 --- a/bindings/perl/README +++ b/bindings/perl/README @@ -28,13 +28,13 @@ INSTALLATION DEPENDENCIES This module requires the "oping" library to be installed. The library is - available at . + available at . COPYRIGHT AND LICENSE Copyright (C) 2007 by Olivier Fredj - Copyright (C) 2008,2009 by Florian Forster + Copyright (C) 2008,2009 by Florian Forster 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 diff --git a/bindings/perl/lib/Net/Oping.pm b/bindings/perl/lib/Net/Oping.pm index bc109dc..ba67f73 100644 --- a/bindings/perl/lib/Net/Oping.pm +++ b/bindings/perl/lib/Net/Oping.pm @@ -19,7 +19,7 @@ # # Authors: # Olivier Fredj -# Florian octo Forster +# Florian octo Forster # 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. Its purpose it to send -C packets (also known as "ping") to a host and measure the -time that elapses until the reception of an C packet (also -known as "pong"). If no such packet is received after a certain timeout the -host is considered to be unreachable. +L. Its purpose it to send C +packets (also known as "ping") to a host and measure the time that elapses +until the reception of an C 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 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 capability. L -The I homepage may be found at L. +The I homepage may be found at L. Information about its mailing list may be found at L. @@ -445,8 +445,7 @@ Perl interface by Florian Forster. Copyright (C) 2007 by Olivier Fredj EofredjEatEproxad.netE -Copyright (C) 2008,2009 by Florian Forster -EoctoEatEverplant.orgE +Copyright (C) 2008,2009 by Florian Forster EffEatEocto.itE 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, diff --git a/configure.ac b/configure.ac index a4eb289..60138fc 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/src/Makefile.am b/src/Makefile.am index 1326783..b8571aa 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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: diff --git a/src/liboping.c b/src/liboping.c index 776f24f..31b8813 100644 --- a/src/liboping.c +++ b/src/liboping.c @@ -1,6 +1,6 @@ /** * Object oriented C module to send ICMP and ICMPv6 `echo's. - * Copyright (C) 2006-2011 Florian octo Forster + * Copyright (C) 2006-2016 Florian octo Forster * * 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 @@ -17,6 +17,10 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef __APPLE__ +#define __APPLE_USE_RFC_3542 +#endif + #if HAVE_CONFIG_H # include #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 index 0000000..5016623 --- /dev/null +++ b/src/liboping.pc.in @@ -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@ diff --git a/src/mans/liboping.pod b/src/mans/liboping.pod index d2c42f9..c36a89c 100644 --- a/src/mans/liboping.pod +++ b/src/mans/liboping.pod @@ -80,7 +80,7 @@ applicable. =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/oping.pod b/src/mans/oping.pod index e076299..f1e0807 100644 --- a/src/mans/oping.pod +++ b/src/mans/oping.pod @@ -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 ICMP packets, then stop and exit. Send one ICMP packet (per host) each I seconds. This can be a floating-point number to specify sub-second precision. +=item B<-w> I + +Specifies the time to wait for an C 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 Set the IP Time to Live to I. This must be a number between (and @@ -79,6 +85,12 @@ real user ID (as returned by L) and the effective user ID (as returned by L) differ, the only argument allowed for this option is "-" (i.e. standard input). +=item B<-O> I + +Write measurements in I (CSV) format to I. +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 Specify the I (QoS) for outgoing packets. This is a @@ -166,6 +178,131 @@ I (ECN), even if the deprecated I (ToS) aliases were used to specify the bits of outgoing packets. +=item B<-m> I + +I Sets the I (an integer number) on outgoing packets. This +can be used by L and other networking infrastructure for filtering +and routing. + +=item B<-u>|B<-U> + +I 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|B|B|B + +I Selects the graph to display. + +=over 4 + +=item B + +Do not show a graph. + +=item B + +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 + +Show a I 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 + +Show a I of the round-trip times. The width of the window is taken +as round-trip time from 0ms on the left to the I (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 + +Configures the latency percentile to report. I 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<-cE5> 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 + +If any hosts have a drop rate higher than I, where I 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 replies for I +hosts have been received. + +The exit status will indicate the number of hosts with more than I +packets lost, up to a number of 255 failing hosts. + =back =head1 COLORS @@ -175,22 +312,19 @@ If supported by the terminal, I 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 -round-trip time and the I. 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. 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, L, L +L, L, L =head1 AUTHOR -liboping is written by Florian octo Forster Eff at octo.itE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2010 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_construct.pod b/src/mans/ping_construct.pod index da082c8..019538c 100644 --- a/src/mans/ping_construct.pod +++ b/src/mans/ping_construct.pod @@ -32,7 +32,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_get_error.pod b/src/mans/ping_get_error.pod index df9a015..23ab2a7 100644 --- a/src/mans/ping_get_error.pod +++ b/src/mans/ping_get_error.pod @@ -24,7 +24,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_host_add.pod b/src/mans/ping_host_add.pod index 254ac35..aaa18d3 100644 --- a/src/mans/ping_host_add.pod +++ b/src/mans/ping_host_add.pod @@ -46,7 +46,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_iterator_get.pod b/src/mans/ping_iterator_get.pod index 860f448..74d2858 100644 --- a/src/mans/ping_iterator_get.pod +++ b/src/mans/ping_iterator_get.pod @@ -44,7 +44,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_iterator_get_context.pod b/src/mans/ping_iterator_get_context.pod index f0585ab..82bc0ab 100644 --- a/src/mans/ping_iterator_get_context.pod +++ b/src/mans/ping_iterator_get_context.pod @@ -39,7 +39,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_iterator_get_info.pod b/src/mans/ping_iterator_get_info.pod index 744a552..849e156 100644 --- a/src/mans/ping_iterator_get_info.pod +++ b/src/mans/ping_iterator_get_info.pod @@ -129,7 +129,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_send.pod b/src/mans/ping_send.pod index 8237dae..8bf9bd8 100644 --- a/src/mans/ping_send.pod +++ b/src/mans/ping_send.pod @@ -36,7 +36,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2009 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/mans/ping_setopt.pod b/src/mans/ping_setopt.pod index 582b355..f8c4ff6 100644 --- a/src/mans/ping_setopt.pod +++ b/src/mans/ping_setopt.pod @@ -75,6 +75,12 @@ C (IPv4) or C (IPv6) option. It is the caller's responsibility to chose a valid bit combination. For details, read the L and L manual pages, as well as I2474>. +=item B + +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 on platforms which don't have SO_MARK. + =back The I argument is a pointer to the new value. It must not be NULL. It is @@ -92,7 +98,7 @@ L =head1 AUTHOR -liboping is written by Florian octo Forster Eocto at verplant.orgE. -Its homepage can be found at L. +liboping is written by Florian "octo" Forster Eff at octo.itE. +Its homepage can be found at L. -(c) 2005-2010 by Florian octo Forster. +Copyright (c) 2005-2016 by Florian "octo" Forster. diff --git a/src/oping.c b/src/oping.c index 79d7569..528c90d 100644 --- a/src/oping.c +++ b/src/oping.c @@ -1,6 +1,6 @@ /** * Object oriented C module to send ICMP and ICMPv6 `echo's. - * Copyright (C) 2006-2011 Florian octo Forster + * Copyright (C) 2006-2016 Florian octo Forster * * 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 @@ -74,21 +74,84 @@ #include #endif +#include +#include + #if USE_NCURSES # define NCURSES_OPAQUE 1 -# include +/* 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 +#elif defined HAVE_NCURSESW_H +# include +#elif defined HAVE_NCURSES_CURSES_H +# include +#elif defined HAVE_NCURSES_H +# include +#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 \n" + " -O filename write RTT measurements to \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 \n" + "\noping "PACKAGE_VERSION", http://noping.cc/\n" + "by Florian octo Forster \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 : */ diff --git a/src/oping.h b/src/oping.h index e53d139..1970ef2 100644 --- a/src/oping.h +++ b/src/oping.h @@ -1,6 +1,6 @@ /** * Object oriented C module to send ICMP and ICMPv6 `echo's. - * Copyright (C) 2006-2011 Florian octo Forster + * Copyright (C) 2006-2016 Florian octo Forster * * 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