Update copyright notices.
[liboping.git] / src / liboping.c
index edc3522..eca5c16 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * Object oriented C module to send ICMP and ICMPv6 `echo's.
- * Copyright (C) 2006-2016  Florian octo Forster <ff at octo.it>
+ * Copyright (C) 2006-2017  Florian octo Forster <ff at octo.it>
  *
  * This library is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by the
@@ -957,18 +957,19 @@ static pinghost_t *ping_alloc (void)
 
 static void ping_free (pinghost_t *ph)
 {
-       if (ph->username != NULL)
-               free (ph->username);
-
-       if (ph->hostname != NULL)
-               free (ph->hostname);
+       if (ph == NULL)
+               return;
 
-       if (ph->data != NULL)
-               free (ph->data);
+       free (ph->username);
+       free (ph->hostname);
+       free (ph->data);
 
        free (ph);
 }
 
+/* ping_open_socket opens, initializes and returns a new raw socket to use for
+ * ICMPv4 or ICMPv6 packets. addrfam must be either AF_INET or AF_INET6. On
+ * error, -1 is returned and obj->errmsg is set appropriately. */
 static int ping_open_socket(pingobj_t *obj, int addrfam)
 {
        int fd;
@@ -982,27 +983,28 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
        }
        else /* this should not happen */
        {
+               ping_set_error (obj, "ping_open_socket", "Unknown address family");
                dprintf ("Unknown address family: %i\n", addrfam);
-               return (-1);
+               return -1;
        }
 
        if (fd == -1)
        {
+               ping_set_errno (obj, errno);
 #if WITH_DEBUG
                char errbuf[PING_ERRMSG_LEN];
                dprintf ("socket: %s\n",
                                sstrerror (errno, errbuf, sizeof (errbuf)));
 #endif
-               ping_set_errno (obj, errno);
                return -1;
        }
        else if (fd >= FD_SETSIZE)
        {
+               ping_set_errno (obj, EMFILE);
                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;
        }
 
@@ -1013,12 +1015,12 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
 
                if (bind (fd, obj->srcaddr, obj->srcaddrlen) == -1)
                {
+                       ping_set_errno (obj, errno);
 #if WITH_DEBUG
                        char errbuf[PING_ERRMSG_LEN];
                        dprintf ("bind: %s\n",
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
 #endif
-                       ping_set_errno (obj, errno);
                        close (fd);
                        return -1;
                }
@@ -1030,12 +1032,12 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
                if (setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE,
                                obj->device, strlen (obj->device) + 1) != 0)
                {
+                       ping_set_errno (obj, errno);
 #if WITH_DEBUG
                        char errbuf[PING_ERRMSG_LEN];
                        dprintf ("setsockopt (SO_BINDTODEVICE): %s\n",
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
 #endif
-                       ping_set_errno (obj, errno);
                        close (fd);
                        return -1;
                }
@@ -1061,20 +1063,16 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
 #ifdef SO_TIMESTAMP
        if (1) /* {{{ */
        {
-               int status;
-               int opt = 1;
-
-               status = setsockopt (fd,
-                               SOL_SOCKET, SO_TIMESTAMP,
-                               &opt, sizeof (opt));
+               int status = setsockopt (fd, SOL_SOCKET, SO_TIMESTAMP,
+                                        &(int){1}, sizeof(int));
                if (status != 0)
                {
+                       ping_set_errno (obj, errno);
 #if WITH_DEBUG
                        char errbuf[PING_ERRMSG_LEN];
                        dprintf ("setsockopt (SO_TIMESTAMP): %s\n",
                                        sstrerror (errno, errbuf, sizeof (errbuf)));
 #endif
-                       ping_set_errno (obj, errno);
                        close (fd);
                        return -1;
                }
@@ -1083,37 +1081,27 @@ static int ping_open_socket(pingobj_t *obj, int addrfam)
 
        if (addrfam == AF_INET)
        {
-               int opt;
-
 #ifdef IP_RECVTOS
                /* Enable receiving the TOS field */
-               opt = 1;
-               setsockopt (fd, IPPROTO_IP, IP_RECVTOS,
-                               &opt, sizeof (opt));
+               setsockopt (fd, IPPROTO_IP, IP_RECVTOS, &(int){1}, sizeof(int));
 #endif /* IP_RECVTOS */
 
                /* Enable receiving the TTL field */
-               opt = 1;
-               setsockopt (fd, IPPROTO_IP, IP_RECVTTL,
-                               &opt, sizeof (opt));
+               setsockopt (fd, IPPROTO_IP, IP_RECVTTL, &(int){1}, sizeof(int));
        }
 #if defined(IPV6_RECVHOPLIMIT) || defined(IPV6_RECVTCLASS)
        else if (addrfam == AF_INET6)
        {
-               int opt;
-
 # if defined(IPV6_RECVHOPLIMIT)
                /* For details see RFC 3542, section 6.3. */
-               opt = 1;
                setsockopt (fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
-                               &opt, sizeof (opt));
+                           &(int){1}, sizeof(int));
 # endif /* IPV6_RECVHOPLIMIT */
 
 # if defined(IPV6_RECVTCLASS)
                /* For details see RFC 3542, section 6.5. */
-               opt = 1;
                setsockopt (fd, IPPROTO_IPV6, IPV6_RECVTCLASS,
-                               &opt, sizeof (opt));
+                           &(int){1}, sizeof(int));
 # endif /* IPV6_RECVTCLASS */
        }
 #endif /* IPV6_RECVHOPLIMIT || IPV6_RECVTCLASS */
@@ -1135,9 +1123,9 @@ pingobj_t *ping_construct (void)
 {
        pingobj_t *obj;
 
-       if ((obj = (pingobj_t *) malloc (sizeof (pingobj_t))) == NULL)
+       if ((obj = malloc (sizeof (*obj))) == NULL)
                return (NULL);
-       memset (obj, 0, sizeof (pingobj_t));
+       memset (obj, 0, sizeof (*obj));
 
        obj->timeout    = PING_DEF_TIMEOUT;
        obj->ttl        = PING_DEF_TTL;
@@ -1153,29 +1141,22 @@ pingobj_t *ping_construct (void)
 void ping_destroy (pingobj_t *obj)
 {
        pinghost_t *current;
-       pinghost_t *next;
 
        if (obj == NULL)
                return;
 
        current = obj->head;
-       next = NULL;
 
        while (current != NULL)
        {
-               next = current->next;
+               pinghost_t *next = current->next;
                ping_free (current);
                current = next;
        }
 
-       if (obj->data != NULL)
-               free (obj->data);
-
-       if (obj->srcaddr != NULL)
-               free (obj->srcaddr);
-
-       if (obj->device != NULL)
-               free (obj->device);
+       free (obj->data);
+       free (obj->srcaddr);
+       free (obj->device);
 
        if (obj->fd4 != -1)
                close(obj->fd4);
@@ -1357,66 +1338,49 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
 
 int ping_send (pingobj_t *obj)
 {
-       fd_set read_fds;
-       fd_set write_fds;
-       fd_set err_fds;
-
-       int num_fds;
-       int max_fd;
-
-       pinghost_t *ph;
        pinghost_t *ptr;
 
        struct timeval endtime;
        struct timeval nowtime;
        struct timeval timeout;
-       int status;
-
-       int pings = 0;
-       int ret = 0;
-
-       ph = obj->head;
 
-       int fd4 = obj->fd4;
-       int fd6 = obj->fd6;
+       _Bool need_ipv4_socket = 0;
+       _Bool need_ipv6_socket = 0;
 
-       for (ptr = ph; ptr != NULL; ptr = ptr->next)
+       for (ptr = obj->head; ptr != NULL; ptr = ptr->next)
        {
-               if (fd6 == -1 && ptr->addrfamily == AF_INET6)
-               {
-                       obj->fd6 = fd6 = ping_open_socket(obj, AF_INET6);
-                       ping_set_ttl (obj, obj->ttl);
-                       ping_set_qos (obj, obj->qos);
-               }
-               else if (fd4 == -1 && ptr->addrfamily == AF_INET)
-               {
-                       obj->fd4 = fd4 = ping_open_socket(obj, AF_INET);
-                       ping_set_ttl (obj, obj->ttl);
-                       ping_set_qos (obj, obj->qos);
-               }
-
-               if ((fd6 == -1 && ptr->addrfamily == AF_INET6)
-                       || (fd4 == -1 && ptr->addrfamily == AF_INET))
-               {
-#if WITH_DEBUG
-                       char errbuf[PING_ERRMSG_LEN];
-                       dprintf ("socket: %s\n",
-                                       sstrerror (errno, errbuf, sizeof (errbuf)));
-#endif
-                       ping_set_errno (obj, errno);
-                       return (-1);
-               }
-
                ptr->latency  = -1.0;
                ptr->recv_ttl = -1;
+
+               if (ptr->addrfamily == AF_INET)
+                       need_ipv4_socket = 1;
+               else if (ptr->addrfamily == AF_INET6)
+                       need_ipv6_socket = 1;
        }
 
-       if (fd4 == -1 && fd6 == -1)
+       if (!need_ipv4_socket && !need_ipv6_socket)
        {
-               dprintf("No sockets to use\n");
+               ping_set_error (obj, "ping_send", "No hosts to ping");
                return (-1);
        }
 
+       if (need_ipv4_socket && obj->fd4 == -1)
+       {
+               obj->fd4 = ping_open_socket(obj, AF_INET);
+               if (obj->fd4 == -1)
+                       return (-1);
+               ping_set_ttl (obj, obj->ttl);
+               ping_set_qos (obj, obj->qos);
+       }
+       if (need_ipv6_socket && obj->fd6 == -1)
+       {
+               obj->fd6 = ping_open_socket(obj, AF_INET6);
+               if (obj->fd6 == -1)
+                       return (-1);
+               ping_set_ttl (obj, obj->ttl);
+               ping_set_qos (obj, obj->qos);
+       }
+
        if (gettimeofday (&nowtime, NULL) == -1)
        {
                ping_set_errno (obj, errno);
@@ -1433,37 +1397,59 @@ int ping_send (pingobj_t *obj)
 
        ping_timeval_add (&nowtime, &timeout, &endtime);
 
-       ptr = ph;
-       num_fds = 0;
-       if (fd4 != -1)
-               num_fds++;
-       if (fd6 != -1)
-               num_fds++;
-       max_fd = (fd4 > fd6) ? fd4 : fd6;
-       assert (max_fd < FD_SETSIZE);
+       /* host_to_ping points to the host to which to send the next ping. The
+        * pointer is advanced to the next host in the linked list after the
+        * ping has been sent. If host_to_ping is NULL, no more pings need to be
+        * send out. */
+       pinghost_t *host_to_ping = obj->head;
+
+       /* pings_in_flight is the number of hosts we sent a "ping" to but didn't
+        * receive a "pong" yet. */
+       int pings_in_flight = 0;
+
+       /* pongs_received is the number of echo replies received. Unless there
+        * is an error, this is used as the return value of ping_send(). */
+       int pongs_received = 0;
 
-       while (pings > 0 || ptr != NULL)
+       int error_count = 0;
+
+       while (pings_in_flight > 0 || host_to_ping != NULL)
        {
+               fd_set read_fds;
+               fd_set write_fds;
+
+               int write_fd = -1;
+               int max_fd = -1;
+
                FD_ZERO (&read_fds);
                FD_ZERO (&write_fds);
-               FD_ZERO (&err_fds);
 
-               if (fd4 != -1)
+               if (obj->fd4 != -1)
                {
-                       FD_SET(fd4, &read_fds);
-                       if (ptr != NULL && ptr->addrfamily == AF_INET)
-                               FD_SET(fd4, &write_fds);
-                       FD_SET(fd4, &err_fds);
+                       FD_SET(obj->fd4, &read_fds);
+                       if (host_to_ping != NULL && host_to_ping->addrfamily == AF_INET)
+                               write_fd = obj->fd4;
+
+                       if (max_fd < obj->fd4)
+                               max_fd = obj->fd4;
                }
 
-               if (fd6 != -1)
+               if (obj->fd6 != -1)
                {
-                       FD_SET(fd6, &read_fds);
-                       if (ptr != NULL && ptr->addrfamily == AF_INET6)
-                               FD_SET(fd6, &write_fds);
-                       FD_SET(fd6, &err_fds);
+                       FD_SET(obj->fd6, &read_fds);
+                       if (host_to_ping != NULL && host_to_ping->addrfamily == AF_INET6)
+                               write_fd = obj->fd6;
+
+                       if (max_fd < obj->fd6)
+                               max_fd = obj->fd6;
                }
 
+               if (write_fd != -1)
+                       FD_SET(write_fd, &write_fds);
+
+               assert (max_fd != -1);
+               assert (max_fd < FD_SETSIZE);
+
                if (gettimeofday (&nowtime, NULL) == -1)
                {
                        ping_set_errno (obj, errno);
@@ -1473,11 +1459,12 @@ int ping_send (pingobj_t *obj)
                if (ping_timeval_sub (&endtime, &nowtime, &timeout) == -1)
                        break;
 
-               dprintf ("Waiting on %i sockets for %i.%06i seconds\n", num_fds,
-                               (int) timeout.tv_sec,
-                               (int) timeout.tv_usec);
+               dprintf ("Waiting on %i sockets for %u.%06u seconds\n",
+                               ((obj->fd4 != -1) ? 1 : 0) + ((obj->fd6 != -1) ? 1 : 0),
+                               (unsigned) timeout.tv_sec,
+                               (unsigned) timeout.tv_usec);
 
-               status = select (max_fd + 1, &read_fds, &write_fds, &err_fds, &timeout);
+               int status = select (max_fd + 1, &read_fds, &write_fds, NULL, &timeout);
 
                if (gettimeofday (&nowtime, NULL) == -1)
                {
@@ -1485,79 +1472,63 @@ int ping_send (pingobj_t *obj)
                        return (-1);
                }
 
-               if ((status == -1) && (errno == EINTR))
+               if (status == -1)
                {
-                       dprintf ("select was interrupted by signal..\n");
-                       ping_set_errno (obj, EINTR);
+                       ping_set_errno (obj, errno);
+                       dprintf ("select: %s\n", obj->errmsg);
                        return (-1);
                }
-               else if (status < 0)
-               {
-#if WITH_DEBUG
-                       char errbuf[PING_ERRMSG_LEN];
-                       dprintf ("select: %s\n",
-                                       sstrerror (errno, errbuf, sizeof (errbuf)));
-#endif
-                       break;
-               }
                else if (status == 0)
                {
                        dprintf ("select timed out\n");
-                       for (ptr = ph; ptr != NULL; ptr = ptr->next)
-                               if (ptr->latency < 0.0)
-                                       ptr->dropped++;
 
+                       pinghost_t *ph;
+                       for (ph = obj->head; ph != NULL; ph = ph->next)
+                               if (ph->latency < 0.0)
+                                       ph->dropped++;
                        break;
                }
 
-               if (fd4 != -1)
+               /* first, check if we can receive a reply ... */
+               if (obj->fd6  != -1 && FD_ISSET (obj->fd6, &read_fds))
                {
-                       if (FD_ISSET (fd4, &read_fds))
+                       if (ping_receive_one (obj, &nowtime, AF_INET6) == 0)
                        {
-                               if (!ping_receive_one(obj, &nowtime, AF_INET))
-                                       --pings;
+                               pings_in_flight--;
+                               pongs_received++;
                        }
-                       else if (ptr != NULL && ptr->addrfamily == AF_INET &&
-                                               FD_ISSET (fd4, &write_fds))
-                       {
-                               if (!ping_send_one(obj, ptr, fd4))
-                               {
-                                       ptr = ptr->next;
-                                       ++pings;
-                               }
-                               else
-                               {
-                                       --ret;
-                               }
-                       }
-
+                       continue;
                }
-
-               if (fd6  != -1)
+               if (obj->fd4 != -1 && FD_ISSET (obj->fd4, &read_fds))
                {
-                       if (FD_ISSET (fd6, &read_fds))
-                       {
-                               if (!ping_receive_one(obj, &nowtime, AF_INET6))
-                                       --pings;
-                       }
-                       else if (ptr != NULL && ptr->addrfamily == AF_INET6 &&
-                                               FD_ISSET (fd6, &write_fds))
+                       if (ping_receive_one (obj, &nowtime, AF_INET) == 0)
                        {
-                               if (!ping_send_one(obj, ptr, fd6))
-                               {
-                                       ++pings;
-                                       ptr = ptr->next;
-                               }
-                               else
-                               {
-                                       --ret;
-                               }
+                               pings_in_flight--;
+                               pongs_received++;
                        }
+                       continue;
                }
 
+               /* ... and if no reply is available to read, continue sending
+                * out pings. */
+
+               /* this condition should always be true. We keep it for
+                * consistency with the read blocks above and just to be on the
+                * safe side. */
+               if (write_fd != -1 && FD_ISSET (write_fd, &write_fds))
+               {
+                       if (ping_send_one (obj, host_to_ping, write_fd) == 0)
+                               pings_in_flight++;
+                       else
+                               error_count++;
+                       host_to_ping = host_to_ping->next;
+                       continue;
+               }
        } /* while (1) */
 
-       return (ret);
+       if (error_count)
+               return (-1 * error_count);
+       return (pongs_received);
 } /* int ping_send */
 
 static pinghost_t *ping_host_search (pinghost_t *ph, const char *host)
@@ -1807,6 +1778,20 @@ pingobj_iter_t *ping_iterator_next (pingobj_iter_t *iter)
        return ((pingobj_iter_t *) iter->next);
 }
 
+int ping_iterator_count (pingobj_t *obj)
+{
+       if (obj == NULL)
+               return 0;
+
+       int count = 0;
+       pingobj_iter_t *iter = obj->head;
+       while (iter) {
+               count++;
+               iter = iter->next;
+       }
+       return count;
+}
+
 int ping_iterator_get_info (pingobj_iter_t *iter, int info,
                void *buffer, size_t *buffer_len)
 {