Code

src/liboping.c: Provide the TTL on received packets.
[liboping.git] / src / liboping.c
index c93633c8ea7c6cfba58336f394a4d4c9b1c7019e..0f4c595f7c267ae26273faf0033ce6360bcc192e 100644 (file)
@@ -1,6 +1,6 @@
 /**
  * Object oriented C module to send ICMP and ICMPv6 `echo's.
- * Copyright (C) 2006-2008  Florian octo Forster <octo at verplant.org>
+ * Copyright (C) 2006-2009  Florian octo Forster <octo at verplant.org>
  *
  * 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
@@ -112,6 +112,7 @@ struct pinghost
        struct timeval          *timer;
        double                   latency;
        uint32_t                 dropped;
+       int                      recv_ttl;
        char                    *data;
 
        void                    *context;
@@ -126,7 +127,7 @@ struct pingobj
        int                      addrfamily;
        char                    *data;
 
-       struct sockaddr_storage *srcaddr;
+       struct sockaddr         *srcaddr;
        socklen_t                srcaddrlen;
 
        char                     errmsg[PING_ERRMSG_LEN];
@@ -137,11 +138,59 @@ struct pingobj
 /*
  * private (static) functions
  */
+/* Even though Posix requires "strerror_r" to return an "int",
+ * some systems (e.g. the GNU libc) return a "char *" _and_
+ * ignore the second argument ... -tokkee */
+char *sstrerror (int errnum, char *buf, size_t buflen)
+{
+       buf[0] = 0;
+
+#if !HAVE_STRERROR_R
+       {
+               snprintf (buf, buflen, "Error %i (%#x)", errnum, errnum);
+       }
+/* #endif !HAVE_STRERROR_R */
+
+#elif STRERROR_R_CHAR_P
+       {
+               char *temp;
+               temp = strerror_r (errnum, buf, buflen);
+               if (buf[0] == 0)
+               {
+                       if ((temp != NULL) && (temp != buf) && (temp[0] != 0))
+                               strncpy (buf, temp, buflen);
+                       else
+                               strncpy (buf, "strerror_r did not return "
+                                               "an error message", buflen);
+               }
+       }
+/* #endif STRERROR_R_CHAR_P */
+
+#else
+       if (strerror_r (errnum, buf, buflen) != 0)
+       {
+               snprintf (buf, buflen, "Error %i (%#x); "
+                               "Additionally, strerror_r failed.",
+                               errnum, errnum);
+       }
+#endif /* STRERROR_R_CHAR_P */
+
+       buf[buflen - 1] = 0;
+
+       return (buf);
+} /* char *sstrerror */
+
 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';
+       snprintf (obj->errmsg, sizeof (obj->errmsg),
+                       "%s: %s", function, message);
+       obj->errmsg[sizeof (obj->errmsg) - 1] = 0;
+}
+
+static void ping_set_errno (pingobj_t *obj, int error_number)
+{
+       sstrerror (error_number, obj->errmsg, sizeof (obj->errmsg));
 }
 
 static int ping_timeval_add (struct timeval *tv1, struct timeval *tv2,
@@ -162,7 +211,6 @@ static int ping_timeval_add (struct timeval *tv1, struct timeval *tv2,
 static int ping_timeval_sub (struct timeval *tv1, struct timeval *tv2,
                struct timeval *res)
 {
-
        if ((tv1->tv_sec < tv2->tv_sec)
                        || ((tv1->tv_sec == tv2->tv_sec)
                                && (tv1->tv_usec < tv2->tv_usec)))
@@ -171,7 +219,7 @@ static int ping_timeval_sub (struct timeval *tv1, struct timeval *tv2,
        res->tv_sec  = tv1->tv_sec  - tv2->tv_sec;
        res->tv_usec = tv1->tv_usec - tv2->tv_usec;
 
-       assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec > 0)));
+       assert ((res->tv_sec > 0) || ((res->tv_sec == 0) && (res->tv_usec >= 0)));
 
        while (res->tv_usec < 0)
        {
@@ -375,38 +423,113 @@ static pinghost_t *ping_receive_ipv6 (pinghost_t *ph, char *buffer, size_t buffe
 
 static int ping_receive_one (int fd, pinghost_t *ph, struct timeval *now)
 {
-       char   buffer[4096];
-       size_t buffer_len;
-
        struct timeval diff;
-
        pinghost_t *host = NULL;
-
-       struct sockaddr_storage sa;
-       socklen_t               sa_len;
-
-       sa_len = sizeof (sa);
-
-       buffer_len = recvfrom (fd, buffer, sizeof (buffer), 0,
-                       (struct sockaddr *) &sa, &sa_len);
-       if (buffer_len == -1)
+       int family;
+       int recv_ttl;
+       
+       /*
+        * Set up the receive buffer..
+        */
+       struct msghdr msghdr;
+       struct cmsghdr *cmsg;
+       char payload_buffer[4096];
+       ssize_t payload_buffer_len;
+       char control_buffer[4096];
+       struct iovec payload_iovec;
+
+       memset (&payload_iovec, 0, sizeof (payload_iovec));
+       payload_iovec.iov_base = payload_buffer;
+       payload_iovec.iov_len = sizeof (payload_buffer);
+
+       memset (&msghdr, 0, sizeof (msghdr));
+       /* unspecified source address */
+       msghdr.msg_name = NULL;
+       msghdr.msg_namelen = 0;
+       /* output buffer vector, see readv(2) */
+       msghdr.msg_iov = &payload_iovec;
+       msghdr.msg_iovlen = 1;
+       /* output buffer for control messages */
+       msghdr.msg_control = control_buffer;
+       msghdr.msg_controllen = sizeof (control_buffer);
+       /* flags; this is an output only field.. */
+       msghdr.msg_flags = 0;
+
+       payload_buffer_len = recvmsg (fd, &msghdr, /* flags = */ 0);
+       if (payload_buffer_len < 0)
        {
-               dprintf ("recvfrom: %s\n", strerror (errno));
+#if WITH_DEBUG
+               char errbuf[PING_ERRMSG_LEN];
+               dprintf ("recvfrom: %s\n",
+                               sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif
                return (-1);
        }
+       dprintf ("Read %zi bytes from fd = %i\n", payload_buffer_len, fd);
+
+       /* Iterate over all auxiliary data in msghdr */
+       family = -1;
+       recv_ttl = -1;
+       for (cmsg = CMSG_FIRSTHDR (&msghdr); /* {{{ */
+                       cmsg != NULL;
+                       cmsg = CMSG_NXTHDR (&msghdr, cmsg))
+       {
+               if (cmsg->cmsg_level == IPPROTO_IP) /* {{{ */
+               {
+                       family = AF_INET;
+                       if (cmsg->cmsg_type == IP_TTL)
+                       {
+                               memcpy (&recv_ttl, CMSG_DATA (cmsg),
+                                               sizeof (recv_ttl));
+                               dprintf ("TTLv4 = %i;\n", recv_ttl);
+                       }
+                       else
+                       {
+                               dprintf ("Not handling option %i.\n",
+                                               cmsg->cmsg_type);
+                       }
+               } /* }}} */
+               else if (cmsg->cmsg_level == IPPROTO_IPV6) /* {{{ */
+               {
+                       family = AF_INET6;
+                       if (cmsg->cmsg_type == IPV6_HOPLIMIT)
+                       {
+                               memcpy (&recv_ttl, CMSG_DATA (cmsg),
+                                               sizeof (recv_ttl));
+                               dprintf ("TTLv6 = %i;\n", recv_ttl);
+                       }
+                       else
+                       {
+                               dprintf ("Not handling option %i.\n",
+                                               cmsg->cmsg_type);
+                       }
+               } /* }}} */
+               else
+               {
+                       dprintf ("Don't know how to handle "
+                                       "unknown protocol %i.\n",
+                                       cmsg->cmsg_level);
+               }
+       } /* }}} for (cmsg) */
 
-       dprintf ("Read %u bytes from fd = %i\n", (unsigned int) buffer_len, fd);
-
-       if (sa.ss_family == AF_INET)
+       if (family == AF_INET)
        {
-               if ((host = ping_receive_ipv4 (ph, buffer, buffer_len)) == NULL)
+               host = ping_receive_ipv4 (ph, payload_buffer, payload_buffer_len);
+               if (host == NULL)
                        return (-1);
        }
-       else if (sa.ss_family == AF_INET6)
+       else if (family == AF_INET6)
        {
-               if ((host = ping_receive_ipv6 (ph, buffer, buffer_len)) == NULL)
+               host = ping_receive_ipv6 (ph, payload_buffer, payload_buffer_len);
+               if (host == NULL)
                        return (-1);
        }
+       else
+       {
+               dprintf ("ping_receive_one: Unknown address family %i.\n",
+                               family);
+               return (-1);
+       }
 
        dprintf ("rcvd: %12i.%06i\n",
                        (int) now->tv_sec,
@@ -428,6 +551,8 @@ static int ping_receive_one (int fd, pinghost_t *ph, struct timeval *now)
        host->latency  = ((double) diff.tv_usec) / 1000.0;
        host->latency += ((double) diff.tv_sec)  * 1000.0;
 
+       host->recv_ttl = recv_ttl;
+
        timerclear (host->timer);
 
        return (0);
@@ -457,7 +582,7 @@ static int ping_receive_all (pingobj_t *obj)
 
        if (gettimeofday (&nowtime, NULL) == -1)
        {
-               ping_set_error (obj, "gettimeofday", strerror (errno));
+               ping_set_errno (obj, errno);
                return (-1);
        }
 
@@ -494,7 +619,7 @@ static int ping_receive_all (pingobj_t *obj)
 
                if (gettimeofday (&nowtime, NULL) == -1)
                {
-                       ping_set_error (obj, "gettimeofday", strerror (errno));
+                       ping_set_errno (obj, errno);
                        return (-1);
                }
 
@@ -509,7 +634,7 @@ static int ping_receive_all (pingobj_t *obj)
 
                if (gettimeofday (&nowtime, NULL) == -1)
                {
-                       ping_set_error (obj, "gettimeofday", strerror (errno));
+                       ping_set_errno (obj, errno);
                        return (-1);
                }
                
@@ -520,7 +645,11 @@ static int ping_receive_all (pingobj_t *obj)
                }
                else if (status < 0)
                {
-                       dprintf ("select: %s\n", strerror (errno));
+#if WITH_DEBUG
+                       char errbuf[PING_ERRMSG_LEN];
+                       dprintf ("select: %s\n",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif
                        break;
                }
                else if (status == 0)
@@ -574,7 +703,7 @@ static ssize_t ping_sendto (pingobj_t *obj, pinghost_t *ph,
                if (errno == ENETUNREACH)
                        return (0);
 #endif
-               ping_set_error (obj, "sendto", strerror (errno));
+               ping_set_errno (obj, errno);
        }
 
        return (ret);
@@ -686,7 +815,11 @@ static int ping_send_all (pingobj_t *obj)
                 * sending the packet, so I will do that too */
                if (gettimeofday (ptr->timer, NULL) == -1)
                {
-                       dprintf ("gettimeofday: %s\n", strerror (errno));
+#if WITH_DEBUG
+                       char errbuf[PING_ERRMSG_LEN];
+                       dprintf ("gettimeofday: %s\n",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif
                        timerclear (ptr->timer);
                        ret--;
                        continue;
@@ -765,21 +898,25 @@ static int ping_get_ident (void)
                        if (read (fd, &seed, sizeof (seed)) != -1)
                        {
                                did_seed = 1;
-                               dprintf ("Random seed: %i\n", seed);
+                               dprintf ("Random seed:   %#x\n", seed);
                                srandom (seed);
                        }
 
                        close (fd);
                }
+#if WITH_DEBUG
                else
                {
-                       dprintf ("open (/dev/urandom): %s\n", strerror (errno));
+                       char errbuf[PING_ERRMSG_LEN];
+                       dprintf ("open (/dev/urandom): %s\n",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
                }
+#endif
        }
 
        retval = (int) random ();
 
-       dprintf ("Random number: %i\n", retval);
+       dprintf ("Random number: %#x\n", retval);
        
        return (retval);
 }
@@ -947,10 +1084,13 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
                        status = getaddrinfo (hostname, NULL, &ai_hints, &ai_list);
                        if (status != 0)
                        {
+#if defined(EAI_SYSTEM)
+                               char errbuf[PING_ERRMSG_LEN];
+#endif
                                ping_set_error (obj, "getaddrinfo",
 #if defined(EAI_SYSTEM)
                                                (status == EAI_SYSTEM)
-                                               ? strerror (errno) :
+                                               ? sstrerror (errno, errbuf, sizeof (errbuf)) :
 #endif
                                                gai_strerror (status));
                                ret = -1;
@@ -965,17 +1105,16 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
                        if (obj->srcaddr == NULL)
                        {
                                obj->srcaddrlen = 0;
-                               obj->srcaddr = (struct sockaddr_storage *) malloc (sizeof (struct sockaddr_storage));
+                               obj->srcaddr = malloc (sizeof (struct sockaddr_storage));
                                if (obj->srcaddr == NULL)
                                {
-                                       ping_set_error (obj, "malloc",
-                                                       strerror (errno));
+                                       ping_set_errno (obj, errno);
                                        ret = -1;
                                        freeaddrinfo (ai_list);
                                        break;
                                }
                        }
-                       memset ((void *) obj->srcaddr, '\0', sizeof (struct sockaddr_storage));
+                       memset ((void *) obj->srcaddr, 0, sizeof (struct sockaddr_storage));
                        assert (ai_list->ai_addrlen <= sizeof (struct sockaddr_storage));
                        memcpy ((void *) obj->srcaddr, (const void *) ai_list->ai_addr,
                                        ai_list->ai_addrlen);
@@ -1024,9 +1163,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;
@@ -1056,7 +1192,7 @@ int ping_host_add (pingobj_t *obj, const char *host)
        if ((ph->username = strdup (host)) == NULL)
        {
                dprintf ("Out of memory!\n");
-               ping_set_error (obj, "strdup", strerror (errno));
+               ping_set_errno (obj, errno);
                ping_free (ph);
                return (-1);
        }
@@ -1064,7 +1200,7 @@ 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_set_errno (obj, errno);
                ping_free (ph);
                return (-1);
        }
@@ -1073,18 +1209,21 @@ int ping_host_add (pingobj_t *obj, const char *host)
        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_set_errno (obj, errno);
                ping_free (ph);
                return (-1);
        }
 
        if ((ai_return = getaddrinfo (host, NULL, &ai_hints, &ai_list)) != 0)
        {
+#if defined(EAI_SYSTEM)
+               char errbuf[PING_ERRMSG_LEN];
+#endif
                dprintf ("getaddrinfo failed\n");
                ping_set_error (obj, "getaddrinfo",
 #if defined(EAI_SYSTEM)
                                                (ai_return == EAI_SYSTEM)
-                                               ? strerror (errno) :
+                                               ? sstrerror (errno, errbuf, sizeof (errbuf)) :
 #endif
                                gai_strerror (ai_return));
                ping_free (ph);
@@ -1098,30 +1237,13 @@ int ping_host_add (pingobj_t *obj, const char *host)
        {
                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;
                }
@@ -1142,8 +1264,12 @@ int ping_host_add (pingobj_t *obj, const char *host)
                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));
+#if WITH_DEBUG
+                       char errbuf[PING_ERRMSG_LEN];
+                       dprintf ("socket: %s\n",
+                                       sstrerror (errno, errbuf, sizeof (errbuf)));
+#endif
+                       ping_set_errno (obj, errno);
                        continue;
                }
 
@@ -1152,10 +1278,14 @@ int ping_host_add (pingobj_t *obj, const char *host)
                        assert (obj->srcaddrlen > 0);
                        assert (obj->srcaddrlen <= sizeof (struct sockaddr_storage));
 
-                       if (bind (ph->fd, (struct sockaddr *) obj->srcaddr, obj->srcaddrlen) == -1)
+                       if (bind (ph->fd, obj->srcaddr, obj->srcaddrlen) == -1)
                        {
-                               dprintf ("bind: %s\n", strerror (errno));
-                               ping_set_error (obj, "bind", strerror (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 (ph->fd);
                                ph->fd = -1;
                                continue;
@@ -1190,6 +1320,21 @@ int ping_host_add (pingobj_t *obj, const char *host)
                }
 #endif /* AI_CANONNAME */
 
+               if (ph->addrfamily == AF_INET)
+               {
+                       int opt = 1;
+
+                       setsockopt (ph->fd, IPPROTO_IP, IP_RECVTTL,
+                                       &opt, sizeof (opt));
+               }
+               else if (ph->addrfamily == AF_INET6)
+               {
+                       int opt = 1;
+
+                       setsockopt (ph->fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT,
+                                       &opt, sizeof (opt));
+               }
+
                break;
        }
 
@@ -1225,7 +1370,7 @@ int ping_host_add (pingobj_t *obj, const char *host)
        ping_set_ttl (ph, obj->ttl);
 
        return (0);
-}
+} /* int ping_host_add */
 
 int ping_host_remove (pingobj_t *obj, const char *host)
 {
@@ -1293,7 +1438,7 @@ int ping_iterator_get_info (pingobj_iter_t *iter, int info,
                case PING_INFO_HOSTNAME:
                        ret = ENOMEM;
                        *buffer_len = strlen (iter->hostname) + 1;
-                       if (orig_buffer_len <= *buffer_len)
+                       if (orig_buffer_len < *buffer_len)
                                break;
                        /* Since (orig_buffer_len > *buffer_len) `strncpy'
                         * will copy `*buffer_len' and pad the rest of
@@ -1379,6 +1524,15 @@ int ping_iterator_get_info (pingobj_iter_t *iter, int info,
                        strncpy ((char *) buffer, iter->data, orig_buffer_len);
                        ret = 0;
                        break;
+
+               case PING_INFO_RECV_TTL:
+                       ret = ENOMEM;
+                       *buffer_len = sizeof (int);
+                       if (orig_buffer_len < sizeof (int))
+                               break;
+                       *((int *) buffer) = iter->recv_ttl;
+                       ret = 0;
+                       break;
        }
 
        return (ret);