From 968dff4d576ca102525c81beddb36a623890424b Mon Sep 17 00:00:00 2001
From: Stephen Sinclair <radarsat1@gmail.com>
Date: Tue, 29 Aug 2017 14:32:24 -0300
Subject: [PATCH] Resolve hostname using getnameinfo for ipv6 support.

Iterates on interfaces, possibly searching for a pre-selected
interface, and resolves the hostname associated with non-localhost
addresses.

Fixes #56, fix compilation with --enable-ipv6.
---
 src/server.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 src/testlo.c |  7 ++++++
 2 files changed, 72 insertions(+), 9 deletions(-)

diff --git a/src/server.c b/src/server.c
index cf20495..4aff673 100644
--- src/server.c
+++ src/server.c
@@ -55,6 +55,10 @@
 #endif
 #include <sys/un.h>
 #include <arpa/inet.h>
+#include <netinet/in.h>
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#endif
 #endif
 
 #if defined(WIN32) || defined(_MSC_VER)
@@ -298,20 +302,72 @@ void lo_server_resolve_hostname(lo_server s)
     /* Set hostname to empty string */
     hostname[0] = '\0';
 
-#ifdef ENABLE_IPV6
+#if defined(ENABLE_IPV6) && defined(HAVE_GETIFADDRS)
     /* Try it the IPV6 friendly way first */
-    for (it = ai; it; it = it->ai_next) {
-        if (getnameinfo(it->ai_addr, it->ai_addrlen, hostname,
-                        sizeof(hostname), NULL, 0, NI_NAMEREQD) == 0) {
+    do {
+        struct ifaddrs *ifa, *ifa_list;
+        if (getifaddrs(&ifa_list))
             break;
+        ifa = ifa_list;
+
+        while (ifa) {
+            if (!ifa->ifa_addr) {
+                ifa = ifa->ifa_next;
+                continue;
+            }
+
+            if (s->addr_if.iface) {
+                if (s->addr_if.size == sizeof(struct in_addr)
+                    && (ifa->ifa_addr->sa_family == AF_INET))
+                {
+                    struct sockaddr_in *sin = (struct sockaddr_in*)ifa->ifa_addr;
+                    if (memcmp(&sin->sin_addr, &s->addr_if.a.addr, sizeof(struct in_addr))!=0
+                        || (s->addr_if.iface && ifa->ifa_name
+                            && strcmp(s->addr_if.iface, ifa->ifa_name)!=0))
+                    {
+                        ifa = ifa->ifa_next;
+                        continue;
+                    }
+                }
+                else if (s->addr_if.size == sizeof(struct in6_addr)
+                         && (ifa->ifa_addr->sa_family == AF_INET6))
+                {
+                    struct sockaddr_in6 *sin = (struct sockaddr_in6*)ifa->ifa_addr;
+                    if (memcmp(&sin->sin6_addr, &s->addr_if.a.addr6,
+                               sizeof(struct in6_addr))!=0
+                        || (s->addr_if.iface && ifa->ifa_name
+                            && strcmp(s->addr_if.iface, ifa->ifa_name)!=0))
+                    {
+                        ifa = ifa->ifa_next;
+                        continue;
+                    }
+                }
+            }
+
+            if ((ifa->ifa_addr->sa_family == AF_INET
+                 && (!s->addr_if.iface || s->addr_if.size == sizeof(struct in_addr))
+                 && (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), hostname,
+                                 sizeof(hostname), NULL, 0, NI_NAMEREQD) == 0))
+                || (ifa->ifa_addr->sa_family == AF_INET6
+                    && (!s->addr_if.iface || s->addr_if.size == sizeof(struct in6_addr))
+                    && (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in6), hostname,
+                                    sizeof(hostname), NULL, 0, NI_NAMEREQD) == 0)))
+            {
+                /* check to make sure getnameinfo() didn't just set the hostname to "::".
+                   Needed on Darwin. */
+                if (hostname[0] == ':')
+                    hostname[0] = '\0';
+                else if (strcmp(hostname, "localhost")==0)
+                    hostname[0] = '\0';
+                else
+                    break;
+            }
+            ifa = ifa->ifa_next;
         }
-    }
 
-    /* check to make sure getnameinfo() didn't just set the hostname to "::".
-       Needed on Darwin. */
-    if (hostname[0] == ':') {
-        hostname[0] = '\0';
+        freeifaddrs(ifa_list);
     }
+    while (0);
 #endif
 
     /* Fallback to the oldschool (i.e. more reliable) way */
diff --git a/src/testlo.c b/src/testlo.c
index c128d10..c69570c 100644
--- src/testlo.c
+++ src/testlo.c
@@ -704,6 +704,13 @@ void test_multicast(lo_server_thread st)
 
     DOING("test_multicast");
 
+#ifdef ENABLE_IPV6
+    // Print a warning but we let it fail, prefer to actually fix IPv6
+    // support rather than just skip the test!
+    printf("WARNING: Compiled with --enable-ipv6, multicast not supported;"
+           "failure expected.\n");
+#endif
+
     /* test multicast server and sender */
     /* message is sent from st otherwise reply doesn't work */
     ms = lo_server_new_multicast("224.0.1.1", "15432", error);
