Merge branch 'v022/net-unreach'
[liboping.git] / src / liboping.c
index 13bcc92..c42b50f 100644 (file)
@@ -4,8 +4,8 @@
  *
  * 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
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
+ * the Free Software Foundation; only version 2 of the License is
+ * applicable.
  *
  * This program is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -115,14 +115,17 @@ struct pinghost
 
 struct pingobj
 {
-       double      timeout;
-       int         ttl;
-       int         addrfamily;
-       char       *data;
+       double                   timeout;
+       int                      ttl;
+       int                      addrfamily;
+       char                    *data;
+
+       struct sockaddr_storage *srcaddr;
+       socklen_t                srcaddrlen;
 
-       char        errmsg[PING_ERRMSG_LEN];
+       char                     errmsg[PING_ERRMSG_LEN];
 
-       pinghost_t *head;
+       pinghost_t              *head;
 };
 
 /*
@@ -834,6 +837,9 @@ void ping_destroy (pingobj_t *obj)
        if (obj->data != NULL)
                free (obj->data);
 
+       if (obj->srcaddr != NULL)
+               free (obj->srcaddr);
+
        free (obj);
 
        return;
@@ -872,6 +878,11 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
                                obj->addrfamily = PING_DEF_AF;
                                ret = -1;
                        }
+                       if (obj->srcaddr != NULL)
+                       {
+                               free (obj->srcaddr);
+                               obj->srcaddr = NULL;
+                       }
                        break;
 
                case PING_OPT_DATA:
@@ -883,6 +894,63 @@ int ping_setopt (pingobj_t *obj, int option, void *value)
                        obj->data = strdup ((const char *) value);
                        break;
 
+               case PING_OPT_SOURCE:
+               {
+                       char            *hostname = (char *) value;
+                       struct addrinfo  ai_hints;
+                       struct addrinfo *ai_list;
+                       int              status;
+#if WITH_DEBUG
+                       if (obj->addrfamily != AF_UNSPEC)
+                       {
+                               dprintf ("Resetting obj->addrfamily to AF_UNSPEC.\n");
+                       }
+#endif
+                       memset ((void *) &ai_hints, '\0', sizeof (ai_hints));
+                       ai_hints.ai_family = obj->addrfamily = AF_UNSPEC;
+#if defined(AI_ADDRCONFIG)
+                       ai_hints.ai_flags = AI_ADDRCONFIG;
+#endif
+                       status = getaddrinfo (hostname, NULL, &ai_hints, &ai_list);
+                       if (status != 0)
+                       {
+                               ping_set_error (obj, "getaddrinfo",
+                                               status == EAI_SYSTEM
+                                               ? strerror (errno)
+                                               : gai_strerror (status));
+                               ret = -1;
+                               break;
+                       }
+#if WITH_DEBUG
+                       if (ai_list->ai_next != NULL)
+                       {
+                               dprintf ("hostname = `%s' is ambiguous.\n", hostname);
+                       }
+#endif
+                       if (obj->srcaddr == NULL)
+                       {
+                               obj->srcaddrlen = 0;
+                               obj->srcaddr = (struct sockaddr_storage *) malloc (sizeof (struct sockaddr_storage));
+                               if (obj->srcaddr == NULL)
+                               {
+                                       ping_set_error (obj, "malloc",
+                                                       strerror (errno));
+                                       ret = -1;
+                                       freeaddrinfo (ai_list);
+                                       break;
+                               }
+                       }
+                       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);
+                       obj->srcaddrlen = ai_list->ai_addrlen;
+                       obj->addrfamily = ai_list->ai_family;
+
+                       freeaddrinfo (ai_list);
+               } /* case PING_OPT_SOURCE */
+               break;
+
                default:
                        ret = -2;
        } /* switch (option) */
@@ -938,6 +1006,9 @@ int ping_host_add (pingobj_t *obj, const char *host)
 #ifdef AI_ADDRCONFIG
        ai_hints.ai_flags    |= AI_ADDRCONFIG;
 #endif
+#ifdef AI_CANONNAME
+       ai_hints.ai_flags    |= AI_CANONNAME;
+#endif
        ai_hints.ai_family    = obj->addrfamily;
        ai_hints.ai_socktype  = SOCK_RAW;
 
@@ -1031,21 +1102,20 @@ int ping_host_add (pingobj_t *obj, const char *host)
                        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)
+               if (obj->srcaddr != NULL)
                {
-                       dprintf ("bind: %s\n", strerror (errno));
-                       ping_set_error (obj, "bind", strerror (errno));
-                       close (ph->fd);
-                       ph->fd = -1;
-                       continue;
+                       assert (obj->srcaddrlen > 0);
+                       assert (obj->srcaddrlen <= sizeof (struct sockaddr_storage));
+
+                       if (bind (ph->fd, (struct sockaddr *) obj->srcaddr, obj->srcaddrlen) == -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));
@@ -1053,6 +1123,28 @@ int ping_host_add (pingobj_t *obj, const char *host)
                ph->addrlen = ai_ptr->ai_addrlen;
                ph->addrfamily = ai_ptr->ai_family;
 
+#ifdef AI_CANONNAME
+               if ((ai_ptr->ai_canonname != NULL)
+                               && (strcmp (ph->hostname, ai_ptr->ai_canonname) != 0))
+               {
+                       char *old_hostname;
+
+                       dprintf ("ph->hostname = %s; ai_ptr->ai_canonname = %s;\n",
+                                       ph->hostname, ai_ptr->ai_canonname);
+
+                       old_hostname = ph->hostname;
+                       if ((ph->hostname = strdup (ai_ptr->ai_canonname)) == NULL)
+                       {
+                               /* strdup failed, falling back to old hostname */
+                               ph->hostname = old_hostname;
+                       }
+                       else if (old_hostname != NULL)
+                       {
+                               free (old_hostname);
+                       }
+               }
+#endif /* AI_CANONNAME */
+
                break;
        }