src/liboping/liboping.c: Fix a problem with strict aliasing.
[collectd.git] / src / liboping / liboping.c
index a6d2c5d..fb3f843 100644 (file)
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
  */
 
-#include <stdlib.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <errno.h>
-#include <string.h>
+#if HAVE_CONFIG_H
+# include <config.h>
+#endif
 
-#include <assert.h>
+#if STDC_HEADERS
+# include <stdlib.h>
+# include <stdio.h>
+# include <string.h>
+# include <errno.h>
+# include <assert.h>
+#else
+# error "You don't have the standard C99 header files installed"
+#endif /* STDC_HEADERS */
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
+#if HAVE_UNISTD_H
+# include <unistd.h>
+#endif
 
-#include <sys/socket.h>
-#include <netdb.h>
-#include <netinet/ip_icmp.h>
-#include <netinet/icmp6.h>
+#if HAVE_FCNTL_H
+# include <fcntl.h>
+#endif
+#if HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
 
-#include <sys/time.h>
-#include <time.h>
+#if TIME_WITH_SYS_TIME
+# include <sys/time.h>
+# include <time.h>
+#else
+# if HAVE_SYS_TIME_H
+#  include <sys/time.h>
+# else
+#  include <time.h>
+# endif
+#endif
+
+#if HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#if HAVE_NETDB_H
+# include <netdb.h>
+#endif
+
+#if HAVE_NETINET_IN_SYSTM_H
+# include <netinet/in_systm.h>
+#endif
+#if HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+#if HAVE_NETINET_IP_H
+# include <netinet/ip.h>
+#endif
+#if HAVE_NETINET_IP_ICMP_H
+# include <netinet/ip_icmp.h>
+#endif
+#ifdef HAVE_NETINET_IP_VAR_H
+# include <netinet/ip_var.h>
+#endif
+#if HAVE_NETINET_IP6_H
+# include <netinet/ip6.h>
+#endif
+#if HAVE_NETINET_ICMP6_H
+# include <netinet/icmp6.h>
+#endif
 
-#include "liboping.h"
+#include "oping.h"
 
-#if DEBUG
+#if WITH_DEBUG
 # define dprintf(...) printf ("%s[%4i]: %-20s: ", __FILE__, __LINE__, __FUNCTION__); printf (__VA_ARGS__)
 #else
-# define dprintf(format, ...) /**/
+# define dprintf(...) /**/
 #endif
 
-#define PING_DATA "Florian Forster <octo@verplant.org> http://verplant.org/"
+#define PING_ERRMSG_LEN 256
+
+struct pinghost
+{
+       char                    *hostname;
+       struct sockaddr_storage *addr;
+       socklen_t                addrlen;
+       int                      addrfamily;
+       int                      fd;
+       int                      ident;
+       int                      sequence;
+       struct timeval          *timer;
+       double                   latency;
+       char                    *data;
+
+       void                    *context;
+
+       struct pinghost         *next;
+};
+
+struct pingobj
+{
+       double      timeout;
+       int         ttl;
+       int         addrfamily;
+       char       *data;
+
+       char        errmsg[PING_ERRMSG_LEN];
+
+       pinghost_t *head;
+};
+
+/*
+ * private (static) functions
+ */
+static void ping_set_error (pingobj_t *obj, const char *function,
+               const char *message)
+{
+       snprintf (obj->errmsg, PING_ERRMSG_LEN, "%s: %s", function, message);
+       obj->errmsg[PING_ERRMSG_LEN - 1] = '\0';
+}
+
+static int ping_timeval_add (struct timeval *tv1, struct timeval *tv2,
+               struct timeval *res)
+{
+       res->tv_sec  = tv1->tv_sec  + tv2->tv_sec;
+       res->tv_usec = tv1->tv_usec + tv2->tv_usec;
+
+       while (res->tv_usec > 1000000)
+       {
+               res->tv_usec -= 1000000;
+               res->tv_sec++;
+       }
+
+       return (0);
+}
 
 static int ping_timeval_sub (struct timeval *tv1, struct timeval *tv2,
                struct timeval *res)
@@ -98,7 +201,7 @@ static uint16_t ping_icmp4_checksum (char *buf, size_t len)
 static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffer_len)
 {
        struct ip *ip_hdr;
-       struct icmphdr *icmp_hdr;
+       struct icmp *icmp_hdr;
 
        size_t ip_hdr_len;
 
@@ -122,23 +225,23 @@ static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffe
        buffer     += ip_hdr_len;
        buffer_len -= ip_hdr_len;
 
-       if (buffer_len < sizeof (struct icmphdr))
+       if (buffer_len < sizeof (struct icmp))
                return (NULL);
 
-       icmp_hdr = (struct icmphdr *) buffer;
-       buffer     += sizeof (struct icmphdr);
-       buffer_len -= sizeof (struct icmphdr);
+       icmp_hdr = (struct icmp *) buffer;
+       buffer     += sizeof (struct icmp);
+       buffer_len -= sizeof (struct icmp);
 
-       if (icmp_hdr->type != ICMP_ECHOREPLY)
+       if (icmp_hdr->icmp_type != ICMP_ECHOREPLY)
        {
-               dprintf ("Unexpected ICMP type: %i\n", icmp_hdr->type);
+               dprintf ("Unexpected ICMP type: %i\n", icmp_hdr->icmp_type);
                return (NULL);
        }
 
-       recv_checksum = icmp_hdr->checksum;
-       icmp_hdr->checksum = 0;
+       recv_checksum = icmp_hdr->icmp_cksum;
+       icmp_hdr->icmp_cksum = 0;
        calc_checksum = ping_icmp4_checksum ((char *) icmp_hdr,
-                       sizeof (struct icmphdr) + buffer_len);
+                       sizeof (struct icmp) + buffer_len);
 
        if (recv_checksum != calc_checksum)
        {
@@ -147,13 +250,13 @@ static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffe
                return (NULL);
        }
 
-       ident = ntohs (icmp_hdr->un.echo.id);
-       seq   = ntohs (icmp_hdr->un.echo.sequence);
+       ident = ntohs (icmp_hdr->icmp_id);
+       seq   = ntohs (icmp_hdr->icmp_seq);
 
        for (ptr = ph; ptr != NULL; ptr = ptr->next)
        {
                dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
-                               ptr->hostname, ptr->ident, ptr->sequence - 1);
+                               ptr->hostname, ptr->ident, ((ptr->sequence - 1) & 0xFFFF));
 
                if (ptr->addrfamily != AF_INET)
                        continue;
@@ -164,7 +267,7 @@ static pinghost_t *ping_receive_ipv4 (pinghost_t *ph, char *buffer, size_t buffe
                if (ptr->ident != ident)
                        continue;
 
-               if ((ptr->sequence - 1) != seq)
+               if (((ptr->sequence - 1) & 0xFFFF) != seq)
                        continue;
 
                dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
@@ -195,8 +298,8 @@ static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffe
                return (NULL);
 
        icmp_hdr = (struct icmp6_hdr *) buffer;
-       buffer     += sizeof (struct icmphdr);
-       buffer_len -= sizeof (struct icmphdr);
+       buffer     += sizeof (struct icmp);
+       buffer_len -= sizeof (struct icmp);
 
        if (icmp_hdr->icmp6_type != ICMP6_ECHO_REPLY)
        {
@@ -216,7 +319,7 @@ static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffe
        for (ptr = ph; ptr != NULL; ptr = ptr->next)
        {
                dprintf ("hostname = %s, ident = 0x%04x, seq = %i\n",
-                               ptr->hostname, ptr->ident, ptr->sequence - 1);
+                               ptr->hostname, ptr->ident, ((ptr->sequence - 1) & 0xFFFF));
 
                if (ptr->addrfamily != AF_INET6)
                        continue;
@@ -227,7 +330,7 @@ static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffe
                if (ptr->ident != ident)
                        continue;
 
-               if ((ptr->sequence - 1) != seq)
+               if (((ptr->sequence - 1) & 0xFFFF) != seq)
                        continue;
 
                dprintf ("Match found: hostname = %s, ident = 0x%04x, seq = %i\n",
@@ -245,12 +348,11 @@ static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffe
        return (ptr);
 }
 
-static void ping_receive_one (int fd, pinghost_t *ph)
+static int ping_receive_one (int fd, pinghost_t *ph, struct timeval *now)
 {
        char   buffer[4096];
        size_t buffer_len;
 
-       struct timeval now;
        struct timeval diff;
 
        pinghost_t *host = NULL;
@@ -265,40 +367,33 @@ static void ping_receive_one (int fd, pinghost_t *ph)
        if (buffer_len == -1)
        {
                dprintf ("recvfrom: %s\n", strerror (errno));
-               return;
+               return (-1);
        }
 
-       dprintf ("Read %i bytes from fd = %i\n", buffer_len, fd);
+       dprintf ("Read %u bytes from fd = %i\n", (unsigned int) buffer_len, fd);
 
        if (sa.ss_family == AF_INET)
        {
                if ((host = ping_receive_ipv4 (ph, buffer, buffer_len)) == NULL)
-                       return;
+                       return (-1);
        }
        else if (sa.ss_family == AF_INET6)
        {
                if ((host = ping_receive_ipv6 (ph, buffer, buffer_len)) == NULL)
-                       return;
-       }
-
-       if (gettimeofday (&now, NULL) == -1)
-       {
-               dprintf ("gettimeofday: %s\n", strerror (errno));
-               timerclear (host->timer);
-               return;
+                       return (-1);
        }
 
+       dprintf ("rcvd: %12i.%06i\n",
+                       (int) now->tv_sec,
+                       (int) now->tv_usec);
        dprintf ("sent: %12i.%06i\n",
                        (int) host->timer->tv_sec,
                        (int) host->timer->tv_usec);
-       dprintf ("rcvd: %12i.%06i\n",
-                       (int) now.tv_sec,
-                       (int) now.tv_usec);
 
-       if (ping_timeval_sub (&now, host->timer, &diff) < 0)
+       if (ping_timeval_sub (now, host->timer, &diff) < 0)
        {
                timerclear (host->timer);
-               return;
+               return (-1);
        }
 
        dprintf ("diff: %12i.%06i\n",
@@ -309,14 +404,17 @@ static void ping_receive_one (int fd, pinghost_t *ph)
        host->latency += ((double) diff.tv_sec)  * 1000.0;
 
        timerclear (host->timer);
+
+       return (0);
 }
 
-static int ping_receive_all (pinghost_t *ph)
+static int ping_receive_all (pingobj_t *obj)
 {
        fd_set readfds;
        int num_readfds;
        int max_readfds;
 
+       pinghost_t *ph;
        pinghost_t *ptr;
 
        struct timeval endtime;
@@ -324,25 +422,36 @@ static int ping_receive_all (pinghost_t *ph)
        struct timeval timeout;
        int status;
 
-       if (gettimeofday (&endtime, NULL) == -1)
-               return (-1);
-       endtime.tv_sec += 1;
+       int ret;
+
+       ph = obj->head;
+       ret = 0;
 
        for (ptr = ph; ptr != NULL; ptr = ptr->next)
                ptr->latency = -1.0;
 
+       if (gettimeofday (&nowtime, NULL) == -1)
+       {
+               ping_set_error (obj, "gettimeofday", strerror (errno));
+               return (-1);
+       }
+
+       /* Set up timeout */
+       timeout.tv_sec = (time_t) obj->timeout;
+       timeout.tv_usec = (suseconds_t) (1000000 * (obj->timeout - ((double) timeout.tv_sec)));
+
+       dprintf ("Set timeout to %i.%06i seconds\n",
+                       (int) timeout.tv_sec,
+                       (int) timeout.tv_usec);
+
+       ping_timeval_add (&nowtime, &timeout, &endtime);
+
        while (1)
        {
                FD_ZERO (&readfds);
                num_readfds =  0;
                max_readfds = -1;
 
-               if (gettimeofday (&nowtime, NULL) == -1)
-                       return (-1);
-
-               if (ping_timeval_sub (&endtime, &nowtime, &timeout) == -1)
-                       return (0);
-
                for (ptr = ph; ptr != NULL; ptr = ptr->next)
                {
                        if (!timerisset (ptr->timer))
@@ -358,16 +467,30 @@ static int ping_receive_all (pinghost_t *ph)
                if (num_readfds == 0)
                        break;
 
+               if (gettimeofday (&nowtime, NULL) == -1)
+               {
+                       ping_set_error (obj, "gettimeofday", strerror (errno));
+                       return (-1);
+               }
+
+               if (ping_timeval_sub (&endtime, &nowtime, &timeout) == -1)
+                       break;
+
                dprintf ("Waiting on %i sockets for %i.%06i seconds\n", num_readfds,
                                (int) timeout.tv_sec,
                                (int) timeout.tv_usec);
 
                status = select (max_readfds + 1, &readfds, NULL, NULL, &timeout);
+
+               if (gettimeofday (&nowtime, NULL) == -1)
+               {
+                       ping_set_error (obj, "gettimeofday", strerror (errno));
+                       return (-1);
+               }
                
-               if (status == EINTR)
+               if ((status == -1) && (errno == EINTR))
                {
                        dprintf ("select was interrupted by signal..\n");
-                       break; /* XXX */
                        continue;
                }
                else if (status < 0)
@@ -384,12 +507,12 @@ static int ping_receive_all (pinghost_t *ph)
                for (ptr = ph; ptr != NULL; ptr = ptr->next)
                {
                        if (FD_ISSET (ptr->fd, &readfds))
-                               ping_receive_one (ptr->fd, ph);
+                               if (ping_receive_one (ptr->fd, ph, &nowtime) == 0)
+                                       ret++;
                }
-       }
+       } /* while (1) */
        
-       /* FIXME - return correct status */
-       return (0);
+       return (ret);
 }
 
 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
@@ -399,9 +522,39 @@ static int ping_receive_all (pinghost_t *ph)
  * +-> ping_send_one_ipv4                                                    *
  * `-> ping_send_one_ipv6                                                    *
  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
-static int ping_send_one_ipv4 (pinghost_t *ph)
+static ssize_t ping_sendto (pingobj_t *obj, pinghost_t *ph,
+               const void *buf, size_t buflen)
+{
+       ssize_t ret;
+
+       if (gettimeofday (ph->timer, NULL) == -1)
+       {
+               timerclear (ph->timer);
+               return (-1);
+       }
+
+       ret = sendto (ph->fd, buf, buflen, 0,
+                       (struct sockaddr *) ph->addr, ph->addrlen);
+
+       if (ret < 0)
+       {
+#if defined(EHOSTUNREACH)
+               if (errno == EHOSTUNREACH)
+                       return (0);
+#endif
+#if defined(ENETUNREACH)
+               if (errno == ENETUNREACH)
+                       return (0);
+#endif
+               ping_set_error (obj, "sendto", strerror (errno));
+       }
+
+       return (ret);
+}
+
+static int ping_send_one_ipv4 (pingobj_t *obj, pinghost_t *ph)
 {
-       struct icmphdr *icmp4;
+       struct icmp *icmp4;
        int status;
 
        char buf[4096];
@@ -413,30 +566,29 @@ static int ping_send_one_ipv4 (pinghost_t *ph)
        dprintf ("ph->hostname = %s\n", ph->hostname);
 
        memset (buf, '\0', sizeof (buf));
-       icmp4 = (struct icmphdr *) buf;
+       icmp4 = (struct icmp *) buf;
        data  = (char *) (icmp4 + 1);
 
-       icmp4->type             = ICMP_ECHO;
-       icmp4->code             = 0;
-       /* The checksum will be calculated by the TCP/IP stack.  */
-       icmp4->checksum         = 0;
-       icmp4->un.echo.id       = htons (ph->ident);
-       icmp4->un.echo.sequence = htons (ph->sequence);
+       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);
 
-       strcpy (data, PING_DATA);
+       buflen = 4096 - sizeof (struct icmp);
+       strncpy (data, ph->data, buflen);
        datalen = strlen (data);
 
-       buflen = datalen + sizeof (struct icmphdr);
+       buflen = datalen + sizeof (struct icmp);
 
-       icmp4->checksum = ping_icmp4_checksum (buf, buflen);
+       icmp4->icmp_cksum = ping_icmp4_checksum (buf, buflen);
 
        dprintf ("Sending ICMPv4 package with ID 0x%04x\n", ph->ident);
 
-       status = sendto (ph->fd, buf, buflen, 0,
-                       (struct sockaddr *) ph->addr, ph->addrlen);
+       status = ping_sendto (obj, ph, buf, buflen);
        if (status < 0)
        {
-               perror ("sendto");
+               perror ("ping_sendto");
                return (-1);
        }
 
@@ -445,7 +597,7 @@ static int ping_send_one_ipv4 (pinghost_t *ph)
        return (0);
 }
 
-static int ping_send_one_ipv6 (pinghost_t *ph)
+static int ping_send_one_ipv6 (pingobj_t *obj, pinghost_t *ph)
 {
        struct icmp6_hdr *icmp6;
        int status;
@@ -465,22 +617,23 @@ static int ping_send_one_ipv6 (pinghost_t *ph)
        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);
 
-       strcpy (data, PING_DATA);
+       buflen = 4096 - sizeof (struct icmp6_hdr);
+       strncpy (data, ph->data, buflen);
        datalen = strlen (data);
 
        buflen = datalen + sizeof (struct icmp6_hdr);
 
        dprintf ("Sending ICMPv6 package with ID 0x%04x\n", ph->ident);
 
-       status = sendto (ph->fd, buf, buflen, 0,
-                       (struct sockaddr *) ph->addr, ph->addrlen);
+       status = ping_sendto (obj, ph, buf, buflen);
        if (status < 0)
        {
-               perror ("sendto");
+               perror ("ping_sendto");
                return (-1);
        }
 
@@ -489,10 +642,16 @@ static int ping_send_one_ipv6 (pinghost_t *ph)
        return (0);
 }
 
-static int ping_send_all (pinghost_t *ph)
+static int ping_send_all (pingobj_t *obj)
 {
+       pinghost_t *ph;
        pinghost_t *ptr;
 
+       int ret;
+
+       ret = 0;
+       ph = obj->head;
+
        for (ptr = ph; ptr != NULL; ptr = ptr->next)
        {
                /* start timer.. The GNU `ping6' starts the timer before
@@ -501,6 +660,7 @@ static int ping_send_all (pinghost_t *ph)
                {
                        dprintf ("gettimeofday: %s\n", strerror (errno));
                        timerclear (ptr->timer);
+                       ret--;
                        continue;
                }
                else
@@ -511,18 +671,20 @@ static int ping_send_all (pinghost_t *ph)
                if (ptr->addrfamily == AF_INET6)
                {       
                        dprintf ("Sending ICMPv6 echo request to `%s'\n", ptr->hostname);
-                       if (ping_send_one_ipv6 (ptr) != 0)
+                       if (ping_send_one_ipv6 (obj, ptr) != 0)
                        {
                                timerclear (ptr->timer);
+                               ret--;
                                continue;
                        }
                }
                else if (ptr->addrfamily == AF_INET)
                {
                        dprintf ("Sending ICMPv4 echo request to `%s'\n", ptr->hostname);
-                       if (ping_send_one_ipv4 (ptr) != 0)
+                       if (ping_send_one_ipv4 (obj, ptr) != 0)
                        {
                                timerclear (ptr->timer);
+                               ret--;
                                continue;
                        }
                }
@@ -530,14 +692,33 @@ static int ping_send_all (pinghost_t *ph)
                {
                        dprintf ("Unknown address family: %i\n", ptr->addrfamily);
                        timerclear (ptr->timer);
+                       ret--;
                        continue;
                }
 
                ptr->sequence++;
        }
 
-       /* FIXME */
-       return (0);
+       return (ret);
+}
+
+/*
+ * Set the TTL of a socket protocol independently.
+ */
+static int ping_set_ttl (pinghost_t *ph, int ttl)
+{
+       int ret = -2;
+
+       if (ph->addrfamily == AF_INET)
+       {
+               ret = setsockopt (ph->fd, IPPROTO_IP, IP_TTL, &ttl, sizeof (ttl));
+       }
+       else if (ph->addrfamily == AF_INET6)
+       {
+               ret = setsockopt (ph->fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof (ttl));
+       }
+
+       return (ret);
 }
 
 static int ping_get_ident (void)
@@ -605,21 +786,32 @@ static void ping_free (pinghost_t *ph)
        if (ph->hostname != NULL)
                free (ph->hostname);
 
+       if (ph->data != NULL)
+               free (ph->data);
+
        free (ph);
 }
 
 /*
  * public methods
  */
-pingobj_t *ping_construct (int flags)
+const char *ping_get_error (pingobj_t *obj)
+{
+       return (obj->errmsg);
+}
+
+pingobj_t *ping_construct (void)
 {
        pingobj_t *obj;
 
        if ((obj = (pingobj_t *) malloc (sizeof (pingobj_t))) == NULL)
                return (NULL);
+       memset (obj, '\0', sizeof (pingobj_t));
 
-       obj->flags = flags;
-       obj->head = NULL;
+       obj->timeout    = PING_DEF_TIMEOUT;
+       obj->ttl        = PING_DEF_TTL;
+       obj->addrfamily = PING_DEF_AF;
+       obj->data       = strdup (PING_DEF_DATA);
 
        return (obj);
 }
@@ -639,20 +831,77 @@ void ping_destroy (pingobj_t *obj)
                current = next;
        }
 
+       if (obj->data != NULL)
+               free (obj->data);
+
        free (obj);
 
        return;
 }
 
+int ping_setopt (pingobj_t *obj, int option, void *value)
+{
+       int ret = 0;
+
+       switch (option)
+       {
+               case PING_OPT_TIMEOUT:
+                       obj->timeout = *((double *) value);
+                       if (obj->timeout < 0.0)
+                       {
+                               obj->timeout = PING_DEF_TIMEOUT;
+                               ret = -1;
+                       }
+                       break;
+
+               case PING_OPT_TTL:
+                       obj->ttl = *((int *) value);
+                       if ((obj->ttl < 1) || (obj->ttl > 255))
+                       {
+                               obj->ttl = PING_DEF_TTL;
+                               ret = -1;
+                       }
+                       break;
+
+               case PING_OPT_AF:
+                       obj->addrfamily = *((int *) value);
+                       if ((obj->addrfamily != AF_UNSPEC)
+                                       && (obj->addrfamily != AF_INET)
+                                       && (obj->addrfamily != AF_INET6))
+                       {
+                               obj->addrfamily = PING_DEF_AF;
+                               ret = -1;
+                       }
+                       break;
+
+               case PING_OPT_DATA:
+                       if (obj->data != NULL)
+                       {
+                               free (obj->data);
+                               obj->data = NULL;
+                       }
+                       obj->data = strdup ((const char *) value);
+                       break;
+
+               default:
+                       ret = -2;
+       } /* switch (option) */
+
+       return (ret);
+} /* int ping_setopt */
+
+
 int ping_send (pingobj_t *obj)
 {
-       if (ping_send_all (obj->head) < 0)
+       int ret;
+
+       if (ping_send_all (obj) < 0)
                return (-1);
 
-       if (ping_receive_all (obj->head) < 0)
+       if ((ret = ping_receive_all (obj)) < 0)
                return (-2);
 
-       return (0);
+       return (ret);
 }
 
 static pinghost_t *ping_host_search (pinghost_t *ph, const char *host)
@@ -672,9 +921,6 @@ int ping_host_add (pingobj_t *obj, const char *host)
 {
        pinghost_t *ph;
 
-       struct sockaddr_storage sockaddr;
-       socklen_t               sockaddr_len;
-
        struct addrinfo  ai_hints;
        struct addrinfo *ai_list, *ai_ptr;
        int              ai_return;
@@ -685,9 +931,12 @@ int ping_host_add (pingobj_t *obj, const char *host)
                return (0);
 
        memset (&ai_hints, '\0', sizeof (ai_hints));
-       ai_hints.ai_flags    = AI_ADDRCONFIG;
-       ai_hints.ai_family   = PF_UNSPEC;
-       ai_hints.ai_socktype = SOCK_RAW;
+       ai_hints.ai_flags     = 0;
+#ifdef AI_ADDRCONFIG
+       ai_hints.ai_flags    |= AI_ADDRCONFIG;
+#endif
+       ai_hints.ai_family    = obj->addrfamily;
+       ai_hints.ai_socktype  = SOCK_RAW;
 
        if ((ph = ping_alloc ()) == NULL)
        {
@@ -698,6 +947,16 @@ int ping_host_add (pingobj_t *obj, const char *host)
        if ((ph->hostname = strdup (host)) == NULL)
        {
                dprintf ("Out of memory!\n");
+               ping_set_error (obj, "strdup", strerror (errno));
+               ping_free (ph);
+               return (-1);
+       }
+
+       /* obj->data is not garuanteed to be != NULL */
+       if ((ph->data = strdup (obj->data == NULL ? PING_DEF_DATA : obj->data)) == NULL)
+       {
+               dprintf ("Out of memory!\n");
+               ping_set_error (obj, "strdup", strerror (errno));
                ping_free (ph);
                return (-1);
        }
@@ -705,59 +964,68 @@ int ping_host_add (pingobj_t *obj, const char *host)
        if ((ai_return = getaddrinfo (host, NULL, &ai_hints, &ai_list)) != 0)
        {
                dprintf ("getaddrinfo failed\n");
+               ping_set_error (obj, "getaddrinfo",
+                               (ai_return == EAI_SYSTEM)
+                               ? strerror (errno)
+                               : gai_strerror (ai_return));
                ping_free (ph);
                return (-1);
        }
 
+       if (ai_list == NULL)
+               ping_set_error (obj, "getaddrinfo", "No hosts returned");
+
        for (ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next)
        {
                ph->fd = -1;
 
-               sockaddr_len = sizeof (sockaddr);
-               memset (&sockaddr, '\0', sockaddr_len);
-
                if (ai_ptr->ai_family == AF_INET)
                {
-                       struct sockaddr_in *si;
-
-                       si = (struct sockaddr_in *) &sockaddr;
-                       si->sin_family = AF_INET;
-                       si->sin_port   = htons (ph->ident);
-                       si->sin_addr.s_addr = htonl (INADDR_ANY);
-
+                       ai_ptr->ai_socktype = SOCK_RAW;
                        ai_ptr->ai_protocol = IPPROTO_ICMP;
                }
                else if (ai_ptr->ai_family == AF_INET6)
                {
-                       struct sockaddr_in6 *si;
-
-                       si = (struct sockaddr_in6 *) &sockaddr;
-                       si->sin6_family = AF_INET6;
-                       si->sin6_port   = htons (ph->ident);
-                       si->sin6_addr   = in6addr_any;
-
+                       ai_ptr->ai_socktype = SOCK_RAW;
                        ai_ptr->ai_protocol = IPPROTO_ICMPV6;
                }
                else
                {
-                       dprintf ("Unknown `ai_family': %i\n", ai_ptr->ai_family);
+                       char errmsg[PING_ERRMSG_LEN];
+
+                       snprintf (errmsg, PING_ERRMSG_LEN, "Unknown `ai_family': %i", ai_ptr->ai_family);
+                       errmsg[PING_ERRMSG_LEN - 1] = '\0';
+
+                       dprintf (errmsg);
+                       ping_set_error (obj, "getaddrinfo", errmsg);
                        continue;
                }
 
+               /* TODO: Move this to a static function `ping_open_socket' and
+                * call it whenever the socket dies. */
                ph->fd = socket (ai_ptr->ai_family, ai_ptr->ai_socktype, ai_ptr->ai_protocol);
                if (ph->fd == -1)
                {
                        dprintf ("socket: %s\n", strerror (errno));
+                       ping_set_error (obj, "socket", strerror (errno));
                        continue;
                }
 
+/*
+ * The majority vote of operating systems has decided that you don't need to
+ * bind here. This code should be reactivated to bind to a specific address,
+ * though. See the `-I' option of `ping(1)' (GNU).  -octo
+ */
+#if 0
                if (bind (ph->fd, (struct sockaddr *) &sockaddr, sockaddr_len) == -1)
                {
                        dprintf ("bind: %s\n", strerror (errno));
+                       ping_set_error (obj, "bind", strerror (errno));
                        close (ph->fd);
                        ph->fd = -1;
                        continue;
                }
+#endif
 
                assert (sizeof (struct sockaddr_storage) >= ai_ptr->ai_addrlen);
                memset (ph->addr, '\0', sizeof (struct sockaddr_storage));
@@ -777,8 +1045,28 @@ int ping_host_add (pingobj_t *obj, const char *host)
                return (-1);
        }
 
-       ph->next  = obj->head;
-       obj->head = ph;
+       /*
+        * Adding in the front is much easier, but then the iterator will
+        * return the host that was added last as first host. That's just not
+        * nice. -octo
+        */
+       if (obj->head == NULL)
+       {
+               obj->head = ph;
+       }
+       else
+       {
+               pinghost_t *hptr;
+
+               hptr = obj->head;
+               while (hptr->next != NULL)
+                       hptr = hptr->next;
+
+               assert ((hptr != NULL) && (hptr->next == NULL));
+               hptr->next = ph;
+       }
+
+       ping_set_ttl (ph, obj->ttl);
 
        return (0);
 }
@@ -800,7 +1088,10 @@ int ping_host_remove (pingobj_t *obj, const char *host)
        }
 
        if (cur == NULL)
+       {
+               ping_set_error (obj, "ping_host_remove", "Host not found");
                return (-1);
+       }
 
        if (pre == NULL)
                obj->head = cur->next;
@@ -825,12 +1116,105 @@ pingobj_iter_t *ping_iterator_next (pingobj_iter_t *iter)
        return ((pingobj_iter_t *) iter->next);
 }
 
-const char *ping_iterator_get_host (pingobj_iter_t *iter)
+int ping_iterator_get_info (pingobj_iter_t *iter, int info,
+               void *buffer, size_t *buffer_len)
+{
+       int ret = EINVAL;
+
+       size_t orig_buffer_len = *buffer_len;
+
+       switch (info)
+       {
+               case PING_INFO_HOSTNAME:
+                       ret = ENOMEM;
+                       *buffer_len = strlen (iter->hostname);
+                       if (orig_buffer_len <= *buffer_len)
+                               break;
+                       /* Since (orig_buffer_len > *buffer_len) `strncpy'
+                        * will copy `*buffer_len' and pad the rest of
+                        * `buffer' with null-bytes */
+                       strncpy (buffer, iter->hostname, orig_buffer_len);
+                       ret = 0;
+                       break;
+
+               case PING_INFO_ADDRESS:
+                       ret = getnameinfo ((struct sockaddr *) iter->addr,
+                                       iter->addrlen,
+                                       (char *) buffer,
+                                       *buffer_len,
+                                       NULL, 0,
+                                       NI_NUMERICHOST);
+                       if (ret != 0)
+                       {
+                               if ((ret == EAI_MEMORY)
+#ifdef EAI_OVERFLOW
+                                               || (ret == EAI_OVERFLOW)
+#endif
+                                  )
+                                       ret = ENOMEM;
+                               else if (ret == EAI_SYSTEM)
+                                       /* XXX: Not thread-safe! */
+                                       ret = errno;
+                               else
+                                       ret = EINVAL;
+                       }
+                       break;
+
+               case PING_INFO_FAMILY:
+                       ret = ENOMEM;
+                       *buffer_len = sizeof (int);
+                       if (orig_buffer_len < sizeof (int))
+                               break;
+                       *((int *) buffer) = iter->addrfamily;
+                       ret = 0;
+                       break;
+
+               case PING_INFO_LATENCY:
+                       ret = ENOMEM;
+                       *buffer_len = sizeof (double);
+                       if (orig_buffer_len < sizeof (double))
+                               break;
+                       *((double *) buffer) = iter->latency;
+                       ret = 0;
+                       break;
+
+               case PING_INFO_SEQUENCE:
+                       ret = ENOMEM;
+                       *buffer_len = sizeof (unsigned int);
+                       if (orig_buffer_len < sizeof (unsigned int))
+                               break;
+                       *((unsigned int *) buffer) = (unsigned int) iter->sequence;
+                       ret = 0;
+                       break;
+
+               case PING_INFO_IDENT:
+                       ret = ENOMEM;
+                       *buffer_len = sizeof (uint16_t);
+                       if (orig_buffer_len < sizeof (uint16_t))
+                               break;
+                       *((uint16_t *) buffer) = (uint16_t) iter->ident;
+                       ret = 0;
+                       break;
+
+               case PING_INFO_DATA:
+                       ret = ENOMEM;
+                       *buffer_len = strlen (iter->data);
+                       if (orig_buffer_len < *buffer_len)
+                               break;
+                       strncpy ((char *) buffer, iter->data, orig_buffer_len);
+                       ret = 0;
+                       break;
+       }
+
+       return (ret);
+}
+
+void *ping_iterator_get_context (pingobj_iter_t *iter)
 {
-       return (iter->hostname);
+       return (iter->context);
 }
 
-double ping_iterator_get_latency (pingobj_iter_t *iter)
+void ping_iterator_set_context (pingobj_iter_t *iter, void *context)
 {
-       return (iter->latency);
+       iter->context = context;
 }