From b565626751ea6bb69fbe1642c89c8b634b064911 Mon Sep 17 00:00:00 2001
From: Andrew Selivanov <andrew.selivanov@gmail.com>
Date: Wed, 23 Jan 2019 17:09:33 +0300
Subject: [PATCH 04/10] Add ares__sortaddrinfo() to support getaddrinfo()
 sorted results (#239)

This is a port of RFC 6724 compliant sorting function from Android Bionic project:
https://android.googlesource.com/platform/bionic/+/e919b116d35aa7deb24ddece69c491e24c3b0d6f/libc/netbsd/net/getaddrinfo.c

The latest version is essentially the same, except two additional parameters to test connection with (mark/uid):
https://android.googlesource.com/platform/bionic/+/master/libc/dns/net/getaddrinfo.c

Please note that even that version has some restrictions. It doesn't support some rules from RFC 6724:

Rule 3 (Avoid deprecated addresses)
Rule 4 (Prefer home addresses)
Rule 7 (Prefer native transport)

Submitted By: Andrew Selivanov (@ki11roy)
---
 Makefile.inc              |   1 +
 ares.h                    |   1 +
 ares__sortaddrinfo.c      | 495 ++++++++++++++++++++++++++++++++++++++++++++++
 ares_getaddrinfo.3        |  18 +-
 ares_getaddrinfo.c        |  14 +-
 ares_ipv6.h               |   7 +
 ares_private.h            |   1 +
 test/ares-test-ai.h       |  11 --
 test/ares-test-live-ai.cc |  47 +++--
 test/ares-test-mock-ai.cc | 286 +++++++++++++--------------
 test/ares-test.cc         |  58 +++++-
 test/ares-test.h          |  28 ++-
 12 files changed, 759 insertions(+), 208 deletions(-)
 create mode 100644 ares__sortaddrinfo.c

diff --git a/Makefile.inc b/Makefile.inc
index 381cc75..de165cf 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -1,6 +1,7 @@
 
 CSOURCES = ares__close_sockets.c	\
   ares__get_hostent.c			\
+  ares__sortaddrinfo.c			\
   ares__read_line.c			\
   ares__timeval.c			\
   ares_android.c			\
diff --git a/ares.h b/ares.h
index 99e3e0b..af82141 100644
--- a/ares.h
+++ b/ares.h
@@ -309,6 +309,7 @@ typedef int  (*ares_sock_config_callback)(ares_socket_t socket_fd,
 
 typedef void (*ares_addr_callback)(void *arg,
                                    int status,
+                                   int timeouts,
                                    struct ares_addrinfo *res);
 
 CARES_EXTERN int ares_library_init(int flags);
diff --git a/ares__sortaddrinfo.c b/ares__sortaddrinfo.c
new file mode 100644
index 0000000..4d3c8d6
--- /dev/null
+++ b/ares__sortaddrinfo.c
@@ -0,0 +1,495 @@
+/*
+ * Original file name getaddrinfo.c
+ * Lifted from the 'Android Bionic' project with the BSD license.
+ */
+
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 by Andrew Selivanov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "ares_setup.h"
+
+#ifdef HAVE_NETINET_IN_H
+#  include <netinet/in.h>
+#endif
+#ifdef HAVE_NETDB_H
+#  include <netdb.h>
+#endif
+#ifdef HAVE_STRINGS_H
+#  include <strings.h>
+#endif
+
+#include <assert.h>
+#include <limits.h>
+
+#include "ares.h"
+#include "ares_private.h"
+
+struct addrinfo_sort_elem
+{
+  struct ares_addrinfo *ai;
+  int has_src_addr;
+  ares_sockaddr src_addr;
+  int original_order;
+};
+
+#define IPV6_ADDR_MC_SCOPE(a) ((a)->s6_addr[1] & 0x0f)
+
+#define IPV6_ADDR_SCOPE_NODELOCAL       0x01
+#define IPV6_ADDR_SCOPE_INTFACELOCAL    0x01
+#define IPV6_ADDR_SCOPE_LINKLOCAL       0x02
+#define IPV6_ADDR_SCOPE_SITELOCAL       0x05
+#define IPV6_ADDR_SCOPE_ORGLOCAL        0x08
+#define IPV6_ADDR_SCOPE_GLOBAL          0x0e
+
+#define IN_LOOPBACK(a) ((((long int)(a)) & 0xff000000) == 0x7f000000)
+
+/* RFC 4193. */
+#define IN6_IS_ADDR_ULA(a) (((a)->s6_addr[0] & 0xfe) == 0xfc)
+
+/* These macros are modelled after the ones in <netinet/in6.h>. */
+/* RFC 4380, section 2.6 */
+#define IN6_IS_ADDR_TEREDO(a)    \
+        ((*(const uint32_t *)(const void *)(&(a)->s6_addr[0]) == ntohl(0x20010000)))
+/* RFC 3056, section 2. */
+#define IN6_IS_ADDR_6TO4(a)      \
+        (((a)->s6_addr[0] == 0x20) && ((a)->s6_addr[1] == 0x02))
+/* 6bone testing address area (3ffe::/16), deprecated in RFC 3701. */
+#define IN6_IS_ADDR_6BONE(a)      \
+        (((a)->s6_addr[0] == 0x3f) && ((a)->s6_addr[1] == 0xfe))
+
+static int get_scope(const struct sockaddr *addr)
+{
+  if (addr->sa_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+      if (IN6_IS_ADDR_MULTICAST(&addr6->sin6_addr))
+        {
+          return IPV6_ADDR_MC_SCOPE(&addr6->sin6_addr);
+        }
+      else if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr) ||
+               IN6_IS_ADDR_LINKLOCAL(&addr6->sin6_addr))
+        {
+          /*
+           * RFC 4291 section 2.5.3 says loopback is to be treated as having
+           * link-local scope.
+           */
+          return IPV6_ADDR_SCOPE_LINKLOCAL;
+        }
+      else if (IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr))
+        {
+          return IPV6_ADDR_SCOPE_SITELOCAL;
+        }
+      else
+        {
+          return IPV6_ADDR_SCOPE_GLOBAL;
+        }
+    }
+  else if (addr->sa_family == AF_INET)
+    {
+      const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
+      unsigned long int na = ntohl(addr4->sin_addr.s_addr);
+      if (IN_LOOPBACK(na) || /* 127.0.0.0/8 */
+          (na & 0xffff0000) == 0xa9fe0000) /* 169.254.0.0/16 */
+        {
+          return IPV6_ADDR_SCOPE_LINKLOCAL;
+        }
+      else
+        {
+          /*
+           * RFC 6724 section 3.2. Other IPv4 addresses, including private
+           * addresses and shared addresses (100.64.0.0/10), are assigned global
+           * scope.
+           */
+          return IPV6_ADDR_SCOPE_GLOBAL;
+        }
+    }
+  else
+    {
+      /*
+       * This should never happen.
+       * Return a scope with low priority as a last resort.
+       */
+      return IPV6_ADDR_SCOPE_NODELOCAL;
+    }
+}
+
+static int get_label(const struct sockaddr *addr)
+{
+  if (addr->sa_family == AF_INET)
+    {
+      return 4;
+    }
+  else if (addr->sa_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+      if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr))
+        {
+          return 0;
+        }
+      else if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr))
+        {
+          return 4;
+        }
+      else if (IN6_IS_ADDR_6TO4(&addr6->sin6_addr))
+        {
+          return 2;
+        }
+      else if (IN6_IS_ADDR_TEREDO(&addr6->sin6_addr))
+        {
+          return 5;
+        }
+      else if (IN6_IS_ADDR_ULA(&addr6->sin6_addr))
+        {
+          return 13;
+        }
+      else if (IN6_IS_ADDR_V4COMPAT(&addr6->sin6_addr))
+        {
+          return 3;
+        }
+      else if (IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr))
+        {
+          return 11;
+        }
+      else if (IN6_IS_ADDR_6BONE(&addr6->sin6_addr))
+        {
+          return 12;
+        }
+      else
+        {
+          /* All other IPv6 addresses, including global unicast addresses. */
+          return 1;
+        }
+    }
+  else
+    {
+      /*
+       * This should never happen.
+       * Return a semi-random label as a last resort.
+       */
+      return 1;
+    }
+}
+
+/*
+ * Get the precedence for a given IPv4/IPv6 address.
+ * RFC 6724, section 2.1.
+ */
+static int get_precedence(const struct sockaddr *addr)
+{
+  if (addr->sa_family == AF_INET)
+    {
+      return 35;
+    }
+  else if (addr->sa_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+      if (IN6_IS_ADDR_LOOPBACK(&addr6->sin6_addr))
+        {
+          return 50;
+        }
+      else if (IN6_IS_ADDR_V4MAPPED(&addr6->sin6_addr))
+        {
+          return 35;
+        }
+      else if (IN6_IS_ADDR_6TO4(&addr6->sin6_addr))
+        {
+          return 30;
+        }
+      else if (IN6_IS_ADDR_TEREDO(&addr6->sin6_addr))
+        {
+          return 5;
+        }
+      else if (IN6_IS_ADDR_ULA(&addr6->sin6_addr))
+        {
+          return 3;
+        }
+      else if (IN6_IS_ADDR_V4COMPAT(&addr6->sin6_addr) ||
+               IN6_IS_ADDR_SITELOCAL(&addr6->sin6_addr) ||
+               IN6_IS_ADDR_6BONE(&addr6->sin6_addr))
+        {
+          return 1;
+        }
+      else
+        {
+          /* All other IPv6 addresses, including global unicast addresses. */
+          return 40;
+        }
+    }
+  else
+    {
+      return 1;
+    }
+}
+
+/*
+ * Find number of matching initial bits between the two addresses a1 and a2.
+ */
+static int common_prefix_len(const struct in6_addr *a1,
+                             const struct in6_addr *a2)
+{
+  const char *p1 = (const char *)a1;
+  const char *p2 = (const char *)a2;
+  unsigned i;
+  for (i = 0; i < sizeof(*a1); ++i)
+    {
+      int x, j;
+      if (p1[i] == p2[i])
+        {
+          continue;
+        }
+      x = p1[i] ^ p2[i];
+      for (j = 0; j < CHAR_BIT; ++j)
+        {
+          if (x & (1 << (CHAR_BIT - 1)))
+            {
+              return i * CHAR_BIT + j;
+            }
+          x <<= 1;
+        }
+    }
+  return sizeof(*a1) * CHAR_BIT;
+}
+
+/*
+ * Compare two source/destination address pairs.
+ * RFC 6724, section 6.
+ */
+static int rfc6724_compare(const void *ptr1, const void *ptr2)
+{
+  const struct addrinfo_sort_elem *a1 = (const struct addrinfo_sort_elem *)ptr1;
+  const struct addrinfo_sort_elem *a2 = (const struct addrinfo_sort_elem *)ptr2;
+  int scope_src1, scope_dst1, scope_match1;
+  int scope_src2, scope_dst2, scope_match2;
+  int label_src1, label_dst1, label_match1;
+  int label_src2, label_dst2, label_match2;
+  int precedence1, precedence2;
+  int prefixlen1, prefixlen2;
+
+  /* Rule 1: Avoid unusable destinations. */
+  if (a1->has_src_addr != a2->has_src_addr)
+    {
+      return a2->has_src_addr - a1->has_src_addr;
+    }
+
+  /* Rule 2: Prefer matching scope. */
+  scope_src1 = get_scope(&a1->src_addr.sa);
+  scope_dst1 = get_scope(a1->ai->ai_addr);
+  scope_match1 = (scope_src1 == scope_dst1);
+
+  scope_src2 = get_scope(&a2->src_addr.sa);
+  scope_dst2 = get_scope(a2->ai->ai_addr);
+  scope_match2 = (scope_src2 == scope_dst2);
+
+  if (scope_match1 != scope_match2)
+    {
+      return scope_match2 - scope_match1;
+    }
+
+  /* Rule 3: Avoid deprecated addresses.  */
+
+  /* Rule 4: Prefer home addresses.  */
+
+  /* Rule 5: Prefer matching label. */
+  label_src1 = get_label(&a1->src_addr.sa);
+  label_dst1 = get_label(a1->ai->ai_addr);
+  label_match1 = (label_src1 == label_dst1);
+
+  label_src2 = get_label(&a2->src_addr.sa);
+  label_dst2 = get_label(a2->ai->ai_addr);
+  label_match2 = (label_src2 == label_dst2);
+
+  if (label_match1 != label_match2)
+    {
+      return label_match2 - label_match1;
+    }
+
+  /* Rule 6: Prefer higher precedence. */
+  precedence1 = get_precedence(a1->ai->ai_addr);
+  precedence2 = get_precedence(a2->ai->ai_addr);
+  if (precedence1 != precedence2)
+    {
+      return precedence2 - precedence1;
+    }
+
+  /* Rule 7: Prefer native transport.  */
+
+  /* Rule 8: Prefer smaller scope. */
+  if (scope_dst1 != scope_dst2)
+    {
+      return scope_dst1 - scope_dst2;
+    }
+
+  /* Rule 9: Use longest matching prefix. */
+  if (a1->has_src_addr && a1->ai->ai_addr->sa_family == AF_INET6 &&
+      a2->has_src_addr && a2->ai->ai_addr->sa_family == AF_INET6)
+    {
+      const struct sockaddr_in6 *a1_src = &a1->src_addr.sa6;
+      const struct sockaddr_in6 *a1_dst =
+          (const struct sockaddr_in6 *)a1->ai->ai_addr;
+      const struct sockaddr_in6 *a2_src = &a2->src_addr.sa6;
+      const struct sockaddr_in6 *a2_dst =
+          (const struct sockaddr_in6 *)a2->ai->ai_addr;
+      prefixlen1 = common_prefix_len(&a1_src->sin6_addr, &a1_dst->sin6_addr);
+      prefixlen2 = common_prefix_len(&a2_src->sin6_addr, &a2_dst->sin6_addr);
+      if (prefixlen1 != prefixlen2)
+        {
+          return prefixlen2 - prefixlen1;
+        }
+    }
+
+  /*
+   * Rule 10: Leave the order unchanged.
+   * We need this since qsort() is not necessarily stable.
+   */
+  return a1->original_order - a2->original_order;
+}
+
+/*
+ * Find the source address that will be used if trying to connect to the given
+ * address.
+ *
+ * Returns 1 if a source address was found, 0 if the address is unreachable,
+ * and -1 if a fatal error occurred. If 0 or 1, the contents of src_addr are
+ * undefined.
+ */
+static int find_src_addr(ares_channel channel,
+                         const struct sockaddr *addr,
+                         struct sockaddr *src_addr)
+{
+  int sock;
+  int ret;
+  socklen_t len;
+
+  switch (addr->sa_family)
+    {
+    case AF_INET:
+      len = sizeof(struct sockaddr_in);
+      break;
+    case AF_INET6:
+      len = sizeof(struct sockaddr_in6);
+      break;
+    default:
+      /* No known usable source address for non-INET families. */
+      return 0;
+    }
+
+  sock = channel->sock_funcs->asocket(addr->sa_family, SOCK_DGRAM, IPPROTO_UDP, channel->sock_func_cb_data);
+  if (sock == -1)
+    {
+      if (errno == EAFNOSUPPORT)
+        {
+          return 0;
+        }
+      else
+        {
+          return -1;
+        }
+    }
+
+  do
+    {
+      ret = channel->sock_funcs->aconnect(sock, addr, len, channel->sock_func_cb_data);
+    }
+  while (ret == -1 && errno == EINTR);
+
+  if (ret == -1)
+    {
+      channel->sock_funcs->aclose(sock, channel->sock_func_cb_data);
+      return 0;
+    }
+
+  if (getsockname(sock, src_addr, &len) == -1)
+    {
+      channel->sock_funcs->aclose(sock, channel->sock_func_cb_data);
+      return -1;
+    }
+
+  channel->sock_funcs->aclose(sock, channel->sock_func_cb_data);
+  return 1;
+}
+
+/*
+ * Sort the linked list starting at sentinel->ai_next in RFC6724 order.
+ * Will leave the list unchanged if an error occurs.
+ */
+int ares__sortaddrinfo(ares_channel channel, struct ares_addrinfo *list_sentinel)
+{
+  struct ares_addrinfo *cur;
+  int nelem = 0, i;
+  int has_src_addr;
+  struct addrinfo_sort_elem *elems;
+
+  cur = list_sentinel->ai_next;
+  while (cur)
+    {
+      ++nelem;
+      cur = cur->ai_next;
+    }
+  elems = (struct addrinfo_sort_elem *)ares_malloc(
+      nelem * sizeof(struct addrinfo_sort_elem));
+  if (!elems)
+    {
+      return ARES_ENOMEM;
+    }
+
+  /*
+   * Convert the linked list to an array that also contains the candidate
+   * source address for each destination address.
+   */
+  for (i = 0, cur = list_sentinel->ai_next; i < nelem; ++i, cur = cur->ai_next)
+    {
+      assert(cur != NULL);
+      elems[i].ai = cur;
+      elems[i].original_order = i;
+      has_src_addr = find_src_addr(channel, cur->ai_addr, &elems[i].src_addr.sa);
+      if (has_src_addr == -1)
+        {
+          ares_free(elems);
+          return ARES_ENOTFOUND;
+        }
+      elems[i].has_src_addr = has_src_addr;
+    }
+
+  /* Sort the addresses, and rearrange the linked list so it matches the sorted
+   * order. */
+  qsort((void *)elems, nelem, sizeof(struct addrinfo_sort_elem),
+        rfc6724_compare);
+
+  list_sentinel->ai_next = elems[0].ai;
+  for (i = 0; i < nelem - 1; ++i)
+    {
+      elems[i].ai->ai_next = elems[i + 1].ai;
+    }
+  elems[nelem - 1].ai->ai_next = NULL;
+
+  ares_free(elems);
+  return ARES_SUCCESS;
+}
diff --git a/ares_getaddrinfo.3 b/ares_getaddrinfo.3
index 42a43fc..0089227 100644
--- a/ares_getaddrinfo.3
+++ b/ares_getaddrinfo.3
@@ -21,7 +21,7 @@ ares_getaddrinfo \- Initiate a host query by name
 .B #include <ares.h>
 .PP
 .B typedef void (*ares_addr_callback)(void *\fIarg\fP, int \fIstatus\fP,
-.B 	struct ares_addrinfo *\fIresult\fP)
+.B 	int \fItimeouts\fP, struct ares_addrinfo *\fIresult\fP)
 .PP
 .B void ares_getaddrinfo(ares_channel \fIchannel\fP, const char *\fIname\fP,
 .B 	const char* \fIservice\fP, const struct ares_addrinfo *\fIhints\fP,
@@ -98,17 +98,25 @@ On successful completion of the query, the callback argument
 points to a
 .B struct addrinfo
 which is a linked list, where each item contains family and address of
-the requested name. The list is not sorted. The reserved memory has to be
-deleted by
+the requested name. The reserved memory has to be deleted by
 .B ares_freeaddrinfo.
+
+The result is sorted according to RFC6724 except:
+ - Rule 3 (Avoid deprecated addresses)
+ - Rule 4 (Prefer home addresses)
+ - Rule 7 (Prefer native transport)
+
+Please note that the function will attempt a connection
+on each of the resolved addresses as per RFC6724.
 .SH SEE ALSO
 .BR ares_freeaddrinfo (3)
 .SH AUTHOR
 Christian Ammer
+.br
+Andrew Selivanov <andrew.selivanov@gmail.com>
 .SH CAVEATS
 This function is under development. It only supports a minimum feature set
 of the function
 .B getaddrinfo
-defined in RFC-3493. It also does not support the destination address selection
-algorithm defined in RFC-6724.
+defined in RFC-3493
 .br
diff --git a/ares_getaddrinfo.c b/ares_getaddrinfo.c
index b89a29c..ebaeda8 100644
--- a/ares_getaddrinfo.c
+++ b/ares_getaddrinfo.c
@@ -91,20 +91,21 @@ void ares_getaddrinfo(ares_channel channel,
                       const char* node, const char* service,
                       const struct ares_addrinfo* hints,
                       ares_addr_callback callback, void* arg) {
+  struct ares_addrinfo sentinel;
   struct host_query *hquery;
   char *single = NULL;
   int ai_family;
 
   ai_family = hints ? hints->ai_family : AF_UNSPEC;
   if (!is_implemented(ai_family)) {
-    callback(arg, ARES_ENOTIMP, NULL);
+    callback(arg, ARES_ENOTIMP, 0, NULL);
     return;
   }
 
   /* Allocate and fill in the host query structure. */
   hquery = ares_malloc(sizeof(struct host_query));
   if (!hquery) {
-    callback(arg, ARES_ENOMEM, NULL);
+    callback(arg, ARES_ENOMEM, 0, NULL);
     return;
   }
   hquery->ai = NULL;
@@ -115,7 +116,7 @@ void ares_getaddrinfo(ares_channel channel,
   hquery->sent_family = -1; /* nothing is sent yet */
   if (!hquery->name) {
     ares_free(hquery);
-    callback(arg, ARES_ENOMEM, NULL);
+    callback(arg, ARES_ENOMEM, 0, NULL);
     return;
   }
   hquery->callback = callback;
@@ -126,6 +127,9 @@ void ares_getaddrinfo(ares_channel channel,
 
   /* Host file lookup */
   if (file_lookup(hquery->name, ai_family, &hquery->ai) == ARES_SUCCESS) {
+    sentinel.ai_next = hquery->ai;
+    ares__sortaddrinfo(channel, &sentinel);
+    hquery->ai = sentinel.ai_next;
     end_hquery(hquery, ARES_SUCCESS);
   }
   else {
@@ -251,6 +255,7 @@ static int add_to_addrinfo(struct ares_addrinfo** ai,
       front->ai_family = AF_INET;
       front->ai_addr = ares_malloc(sizeof(struct sockaddr_in));
       if (!front->ai_addr) goto nomem;
+      memset(front->ai_addr, 0, sizeof(struct sockaddr_in));
       memcpy(&((struct sockaddr_in*)(front->ai_addr))->sin_addr, *p,
         host->h_length);
     }
@@ -259,6 +264,7 @@ static int add_to_addrinfo(struct ares_addrinfo** ai,
       front->ai_family = AF_INET6;
       front->ai_addr = ares_malloc(sizeof(struct sockaddr_in6));
       if (!front->ai_addr) goto nomem;
+      memset(front->ai_addr, 0, sizeof(struct sockaddr_in6));
       memcpy(&((struct sockaddr_in6*)(front->ai_addr))->sin6_addr, *p,
         host->h_length);
     }
@@ -310,7 +316,7 @@ static void next_dns_lookup(struct host_query *hquery) {
 }
 
 static void end_hquery(struct host_query *hquery, int status) {
-  hquery->callback(hquery->arg, status, hquery->ai);
+  hquery->callback(hquery->arg, status, hquery->timeouts, hquery->ai);
   ares_free(hquery->name);
   ares_free(hquery);
 }
diff --git a/ares_ipv6.h b/ares_ipv6.h
index b0017f1..fdbc21f 100644
--- a/ares_ipv6.h
+++ b/ares_ipv6.h
@@ -32,6 +32,13 @@ struct sockaddr_in6
 };
 #endif
 
+typedef union
+{
+  struct sockaddr     sa;
+  struct sockaddr_in  sa4;
+  struct sockaddr_in6 sa6;
+} ares_sockaddr;
+
 #ifndef HAVE_STRUCT_ADDRINFO
 struct addrinfo
 {
diff --git a/ares_private.h b/ares_private.h
index 8e16256..dcbdf0e 100644
--- a/ares_private.h
+++ b/ares_private.h
@@ -358,6 +358,7 @@ void ares__destroy_servers_state(ares_channel channel);
 int ares__parse_qtype_reply(const unsigned char* abuf, int alen, int* qtype);
 int ares__single_domain(ares_channel channel, const char *name, char **s);
 int ares__cat_domain(const char *name, const char *domain, char **s);
+int ares__sortaddrinfo(ares_channel channel, struct ares_addrinfo *ai);
 
 #if 0 /* Not used */
 long ares__tvdiff(struct timeval t1, struct timeval t2);
diff --git a/test/ares-test-ai.h b/test/ares-test-ai.h
index d558489..7cf27e3 100644
--- a/test/ares-test-ai.h
+++ b/test/ares-test-ai.h
@@ -51,17 +51,6 @@ class DefaultChannelTestAI : public LibraryTest {
   ares_channel channel_;
 };
 
-// Structure that describes the result of an ares_addr_callback invocation.
-struct AIResult {
-  AIResult() : done(), status(), airesult() {}
-  // Whether the callback has been invoked.
-  bool done;
-  // Explicitly provided result information.
-  int status;
-  // Contents of the ares_addrinfo structure, if provided.
-  struct ares_addrinfo* airesult;
-};
-
 }
 }
 
diff --git a/test/ares-test-live-ai.cc b/test/ares-test-live-ai.cc
index 96260fb..ab93587 100644
--- a/test/ares-test-live-ai.cc
+++ b/test/ares-test-live-ai.cc
@@ -15,20 +15,20 @@ namespace test {
 
 MATCHER_P(IncludesAtLeastNumAddresses, n, "") {
   int cnt = 0;
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next)
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next)
     cnt++;
   return cnt >= n;
 }
 
 MATCHER_P(OnlyIncludesAddrType, addrtype, "") {
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next)
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next)
     if (ai->ai_family != addrtype)
       return false;
   return true;
 }
 
 MATCHER_P(IncludesAddrType, addrtype, "") {
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next)
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next)
     if (ai->ai_family == addrtype)
       return true;
   return false;
@@ -44,41 +44,38 @@ void DefaultChannelTestAI::Process() {
 VIRT_NONVIRT_TEST_F(DefaultChannelTestAI, LiveGetHostByNameV4) {
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  AIResult result;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  AddrInfoResult result;
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_SUCCESS, result.status);
-  EXPECT_THAT(result.airesult, IncludesAtLeastNumAddresses(1));
-  EXPECT_THAT(result.airesult, OnlyIncludesAddrType(AF_INET));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_SUCCESS, result.status_);
+  EXPECT_THAT(result.ai_, IncludesAtLeastNumAddresses(1));
+  EXPECT_THAT(result.ai_, OnlyIncludesAddrType(AF_INET));
 }
 
 VIRT_NONVIRT_TEST_F(DefaultChannelTestAI, LiveGetHostByNameV6) {
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET6;
-  AIResult result;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  AddrInfoResult result;
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_SUCCESS, result.status);
-  EXPECT_THAT(result.airesult, IncludesAtLeastNumAddresses(1));
-  EXPECT_THAT(result.airesult, OnlyIncludesAddrType(AF_INET6));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_SUCCESS, result.status_);
+  EXPECT_THAT(result.ai_, IncludesAtLeastNumAddresses(1));
+  EXPECT_THAT(result.ai_, OnlyIncludesAddrType(AF_INET6));
 }
 
 VIRT_NONVIRT_TEST_F(DefaultChannelTestAI, LiveGetHostByNameV4AndV6) {
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_UNSPEC;
-  AIResult result;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  AddrInfoResult result;
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_SUCCESS, result.status);
-  EXPECT_THAT(result.airesult, IncludesAtLeastNumAddresses(2));
-  EXPECT_THAT(result.airesult, IncludesAddrType(AF_INET6));
-  EXPECT_THAT(result.airesult, IncludesAddrType(AF_INET));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_SUCCESS, result.status_);
+  EXPECT_THAT(result.ai_, IncludesAtLeastNumAddresses(2));
+  EXPECT_THAT(result.ai_, IncludesAddrType(AF_INET6));
+  EXPECT_THAT(result.ai_, IncludesAddrType(AF_INET));
 }
 
 }  // namespace test
diff --git a/test/ares-test-mock-ai.cc b/test/ares-test-mock-ai.cc
index a67f811..c293102 100644
--- a/test/ares-test-mock-ai.cc
+++ b/test/ares-test-mock-ai.cc
@@ -16,17 +16,21 @@ namespace ares {
 namespace test {
 
 MATCHER_P(IncludesNumAddresses, n, "") {
+  if(!arg)
+    return false;
   int cnt = 0;
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next)
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next)
     cnt++;
   return n == cnt;
 }
 
 MATCHER_P(IncludesV4Address, address, "") {
+  if(!arg)
+    return false;
   in_addr addressnum = {};
   if (!inet_pton(AF_INET, address, &addressnum))
     return false; // wrong number format?
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next) {
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next) {
     if (ai->ai_family != AF_INET)
       continue;
     if (reinterpret_cast<sockaddr_in*>(ai->ai_addr)->sin_addr.s_addr ==
@@ -37,11 +41,13 @@ MATCHER_P(IncludesV4Address, address, "") {
 }
 
 MATCHER_P(IncludesV6Address, address, "") {
+  if(!arg)
+    return false;
   in6_addr addressnum = {};
   if (!inet_pton(AF_INET6, address, &addressnum)) {
     return false; // wrong number format?
   }
-  for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next) {
+  for (const ares_addrinfo* ai = arg.get(); ai != NULL; ai = ai->ai_next) {
     if (ai->ai_family != AF_INET6)
       continue;
     if (!memcmp(
@@ -69,31 +75,28 @@ TEST_P(MockUDPChannelTestAI, ParallelLookups) {
 
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  AIResult result1;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result1);
-  AIResult result2;
-  ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AICallback, &result2);
-  AIResult result3;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result3);
+  AddrInfoResult result1;
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result1);
+  AddrInfoResult result2;
+  ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result2);
+  AddrInfoResult result3;
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result3);
   Process();
 
-  EXPECT_TRUE(result1.done);
-  EXPECT_EQ(result1.status, ARES_SUCCESS);
-  EXPECT_THAT(result1.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result1.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result1.airesult);
-
-  EXPECT_TRUE(result2.done);
-  EXPECT_EQ(result2.status, ARES_SUCCESS);
-  EXPECT_THAT(result2.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result2.airesult, IncludesV4Address("1.2.3.4"));
-  ares_freeaddrinfo(result2.airesult);
-
-  EXPECT_TRUE(result3.done);
-  EXPECT_EQ(result3.status, ARES_SUCCESS);
-  EXPECT_THAT(result3.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result3.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result3.airesult);
+  EXPECT_TRUE(result1.done_);
+  EXPECT_EQ(result1.status_, ARES_SUCCESS);
+  EXPECT_THAT(result1.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result1.ai_, IncludesV4Address("2.3.4.5"));
+
+  EXPECT_TRUE(result2.done_);
+  EXPECT_EQ(result2.status_, ARES_SUCCESS);
+  EXPECT_THAT(result2.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result2.ai_, IncludesV4Address("1.2.3.4"));
+
+  EXPECT_TRUE(result3.done_);
+  EXPECT_EQ(result3.status_, ARES_SUCCESS);
+  EXPECT_THAT(result3.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result3.ai_, IncludesV4Address("2.3.4.5"));
 }
 
 // UDP to TCP specific test
@@ -109,16 +112,15 @@ TEST_P(MockUDPChannelTestAI, TruncationRetry) {
     .WillOnce(SetReply(&server_, &rsptruncated))
     .WillOnce(SetReply(&server_, &rspok));
 
-  AIResult result;
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV4Address("1.2.3.4"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(result.status_, ARES_SUCCESS);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
 }
 
 // TCP only to prevent retries
@@ -127,14 +129,13 @@ TEST_P(MockTCPChannelTestAI, MalformedResponse) {
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReplyData(&server_, one));
 
-  AIResult result;
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_ETIMEOUT, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_ETIMEOUT, result.status_);
 }
 
 TEST_P(MockTCPChannelTestAI, FormErrResponse) {
@@ -144,15 +145,14 @@ TEST_P(MockTCPChannelTestAI, FormErrResponse) {
   rsp.set_rcode(ns_r_formerr);
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_EFORMERR, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_EFORMERR, result.status_);
 }
 
 TEST_P(MockTCPChannelTestAI, ServFailResponse) {
@@ -162,16 +162,15 @@ TEST_P(MockTCPChannelTestAI, ServFailResponse) {
   rsp.set_rcode(ns_r_servfail);
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
+  EXPECT_TRUE(result.done_);
   // ARES_FLAG_NOCHECKRESP not set, so SERVFAIL consumed
-  EXPECT_EQ(ARES_ECONNREFUSED, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_EQ(ARES_ECONNREFUSED, result.status_);
 }
 
 TEST_P(MockTCPChannelTestAI, NotImplResponse) {
@@ -181,16 +180,15 @@ TEST_P(MockTCPChannelTestAI, NotImplResponse) {
   rsp.set_rcode(ns_r_notimpl);
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
+  EXPECT_TRUE(result.done_);
   // ARES_FLAG_NOCHECKRESP not set, so NOTIMPL consumed
-  EXPECT_EQ(ARES_ECONNREFUSED, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_EQ(ARES_ECONNREFUSED, result.status_);
 }
 
 TEST_P(MockTCPChannelTestAI, RefusedResponse) {
@@ -200,16 +198,15 @@ TEST_P(MockTCPChannelTestAI, RefusedResponse) {
   rsp.set_rcode(ns_r_refused);
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
+  EXPECT_TRUE(result.done_);
   // ARES_FLAG_NOCHECKRESP not set, so REFUSED consumed
-  EXPECT_EQ(ARES_ECONNREFUSED, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_EQ(ARES_ECONNREFUSED, result.status_);
 }
 
 // TODO: make it work
@@ -221,14 +218,13 @@ TEST_P(MockTCPChannelTestAI, RefusedResponse) {
 //  EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
 //    .WillOnce(SetReply(&server_, &rsp));
 //  
-//  AIResult result;
+//  AddrInfoResult result;
 //  struct ares_addrinfo hints = {};
 //  hints.ai_family = AF_INET;
-//  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+//  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
 //  Process();
-//  EXPECT_TRUE(result.done);
-//  EXPECT_EQ(ARES_ENODATA, result.status);
-//  ares_freeaddrinfo(result.airesult);
+//  EXPECT_TRUE(result.done_);
+//  EXPECT_EQ(ARES_ENODATA, result.status_);
 //}
 
 class MockExtraOptsTestAI
@@ -263,17 +259,16 @@ TEST_P(MockExtraOptsTestAI, SimpleQuery) {
     .add_answer(new DNSARR("www.google.com", 100, {2, 3, 4, 5}));
   ON_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_SUCCESS, result.status);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_SUCCESS, result.status_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
 }
 
 class MockFlagsChannelOptsTestAI
@@ -304,15 +299,14 @@ TEST_P(MockNoCheckRespChannelTestAI, ServFailResponse) {
   rsp.set_rcode(ns_r_servfail);
   ON_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_ESERVFAIL, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_ESERVFAIL, result.status_);
 }
 
 TEST_P(MockNoCheckRespChannelTestAI, NotImplResponse) {
@@ -322,15 +316,14 @@ TEST_P(MockNoCheckRespChannelTestAI, NotImplResponse) {
   rsp.set_rcode(ns_r_notimpl);
   ON_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_ENOTIMP, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_ENOTIMP, result.status_);
 }
 
 TEST_P(MockNoCheckRespChannelTestAI, RefusedResponse) {
@@ -340,15 +333,14 @@ TEST_P(MockNoCheckRespChannelTestAI, RefusedResponse) {
   rsp.set_rcode(ns_r_refused);
   ON_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_EREFUSED, result.status);
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_EQ(ARES_EREFUSED, result.status_);
 }
 
 TEST_P(MockChannelTestAI, FamilyV6) {
@@ -360,17 +352,15 @@ TEST_P(MockChannelTestAI, FamilyV6) {
                                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03}));
   ON_CALL(server_, OnRequest("example.com", ns_t_aaaa))
     .WillByDefault(SetReply(&server_, &rsp6));
-  AIResult result;
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET6;
   ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
-                   AICallback, &result);
+                   AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
 }
 
 TEST_P(MockChannelTestAI, FamilyV4) {
@@ -380,17 +370,15 @@ TEST_P(MockChannelTestAI, FamilyV4) {
     .add_answer(new DNSARR("example.com", 100, {2, 3, 4, 5}));
   ON_CALL(server_, OnRequest("example.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp4));
-  AIResult result = {};
+  AddrInfoResult result = {};
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
   ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
-                   AICallback, &result);
+                   AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
 }
 
 TEST_P(MockChannelTestAI, FamilyV4_MultipleAddresses) {
@@ -401,18 +389,16 @@ TEST_P(MockChannelTestAI, FamilyV4_MultipleAddresses) {
     .add_answer(new DNSARR("example.com", 100, {7, 8, 9, 0}));
   ON_CALL(server_, OnRequest("example.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp4));
-  AIResult result = {};
+  AddrInfoResult result = {};
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
   ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
-                   AICallback, &result);
+                   AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(2));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  EXPECT_THAT(result.airesult, IncludesV4Address("7.8.9.0"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(2));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
+  EXPECT_THAT(result.ai_, IncludesV4Address("7.8.9.0"));
 }
 
 TEST_P(MockChannelTestAI, FamilyUnspecified) {
@@ -430,18 +416,16 @@ TEST_P(MockChannelTestAI, FamilyUnspecified) {
     .add_answer(new DNSARR("example.com", 100, {2, 3, 4, 5}));
   ON_CALL(server_, OnRequest("example.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &rsp4));
-  AIResult result;
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_UNSPEC;
   ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
-                   AICallback, &result);
+                   AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(2));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  EXPECT_THAT(result.airesult, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(2));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
+  EXPECT_THAT(result.ai_, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
 }
 
 class MockEDNSChannelTestAI : public MockFlagsChannelOptsTestAI {
@@ -460,16 +444,15 @@ TEST_P(MockEDNSChannelTestAI, RetryWithoutEDNS) {
   EXPECT_CALL(server_, OnRequest("www.google.com", ns_t_a))
     .WillOnce(SetReply(&server_, &rspfail))
     .WillOnce(SetReply(&server_, &rspok));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www.google.com.", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(ARES_SUCCESS, result.status);
-  EXPECT_THAT(result.airesult, IncludesV4Address("1.2.3.4"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("1.2.3.4"));
 }
 
 TEST_P(MockChannelTestAI, SearchDomains) {
@@ -490,16 +473,14 @@ TEST_P(MockChannelTestAI, SearchDomains) {
   ON_CALL(server_, OnRequest("www.third.gov", ns_t_a))
     .WillByDefault(SetReply(&server_, &yesthird));
 
-  AIResult result;
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_INET;
-  ares_getaddrinfo(channel_, "www", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
 }
 
 TEST_P(MockChannelTestAI, SearchDomainsServFailOnAAAA) {
@@ -513,7 +494,7 @@ TEST_P(MockChannelTestAI, SearchDomainsServFailOnAAAA) {
     .add_question(new DNSQuestion("www.first.com", ns_t_a));
   ON_CALL(server_, OnRequest("www.first.com", ns_t_a))
     .WillByDefault(SetReply(&server_, &nofirst4));
-  
+
   DNSPacket nosecond;
   nosecond.set_response().set_aa().set_rcode(ns_r_nxdomain)
     .add_question(new DNSQuestion("www.second.org", ns_t_aaaa));
@@ -525,7 +506,7 @@ TEST_P(MockChannelTestAI, SearchDomainsServFailOnAAAA) {
     .add_answer(new DNSARR("www.second.org", 0x0200, {2, 3, 4, 5}));
   ON_CALL(server_, OnRequest("www.second.org", ns_t_a))
     .WillByDefault(SetReply(&server_, &yessecond4));
-  
+
   DNSPacket failthird;
   failthird.set_response().set_aa().set_rcode(ns_r_servfail)
     .add_question(new DNSQuestion("www.third.gov", ns_t_aaaa));
@@ -536,17 +517,15 @@ TEST_P(MockChannelTestAI, SearchDomainsServFailOnAAAA) {
     .add_question(new DNSQuestion("www.third.gov", ns_t_a));
   ON_CALL(server_, OnRequest("www.third.gov", ns_t_a))
     .WillByDefault(SetReply(&server_, &failthird4));
-  
-  AIResult result;
+
+  AddrInfoResult result;
   struct ares_addrinfo hints = {};
   hints.ai_family = AF_UNSPEC;
-  ares_getaddrinfo(channel_, "www", NULL, &hints, AICallback, &result);
+  ares_getaddrinfo(channel_, "www", NULL, &hints, AddrInfoCallback, &result);
   Process();
-  EXPECT_TRUE(result.done);
-  EXPECT_EQ(result.status, ARES_SUCCESS);
-  EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-  EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-  ares_freeaddrinfo(result.airesult);
+  EXPECT_TRUE(result.done_);
+  EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+  EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
 }
 
 class MockMultiServerChannelTestAI
@@ -556,16 +535,15 @@ class MockMultiServerChannelTestAI
   MockMultiServerChannelTestAI(bool rotate)
     : MockChannelOptsTest(3, GetParam().first, GetParam().second, nullptr, rotate ? ARES_OPT_ROTATE : ARES_OPT_NOROTATE) {}
   void CheckExample() {
-    AIResult result;
+    AddrInfoResult result;
     struct ares_addrinfo hints = {};
     hints.ai_family = AF_INET;
-    ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AICallback, &result);
+    ares_getaddrinfo(channel_, "www.example.com.", NULL, &hints, AddrInfoCallback, &result);
     Process();
-    EXPECT_TRUE(result.done);
-    EXPECT_EQ(result.status, ARES_SUCCESS);
-    EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
-    EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
-    ares_freeaddrinfo(result.airesult);
+    EXPECT_TRUE(result.done_);
+    EXPECT_EQ(result.status_, ARES_SUCCESS);
+    EXPECT_THAT(result.ai_, IncludesNumAddresses(1));
+    EXPECT_THAT(result.ai_, IncludesV4Address("2.3.4.5"));
   }
 };
 
diff --git a/test/ares-test.cc b/test/ares-test.cc
index 7776548..1128e99 100644
--- a/test/ares-test.cc
+++ b/test/ares-test.cc
@@ -565,14 +565,58 @@ void HostCallback(void *data, int status, int timeouts,
   if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
 }
 
-void AICallback(void *data, int status,
-                struct ares_addrinfo *res) {
+std::ostream& operator<<(std::ostream& os, const AddrInfoResult& result) {
+  os << '{';
+  if (result.done_) {
+    os << StatusToString(result.status_) << " " << result.ai_;
+  } else {
+    os << "(incomplete)";
+  }
+  os << '}';
+  return os;
+}
+
+std::ostream& operator<<(std::ostream& os, const AddrInfo& ai) {
+  os << '{';
+  struct ares_addrinfo *next = ai.get();
+  while(next) {
+    if(next->ai_canonname) {
+      os << "'" << next->ai_canonname << "' ";
+    }
+    unsigned short port = 0;
+    os << "addr=[";
+    if(next->ai_family == AF_INET) {
+      sockaddr_in* sin = (sockaddr_in*)next->ai_addr;
+      port = ntohs(sin->sin_port);
+      os << AddressToString(&sin->sin_addr, 4);
+    }
+    else if (next->ai_family == AF_INET6) {
+      sockaddr_in6* sin = (sockaddr_in6*)next->ai_addr;
+      port = ntohs(sin->sin6_port);
+      os << "[" << AddressToString(&sin->sin6_addr, 16) << "]";
+    }
+    else
+      os << "unknown family";
+    if(port) {
+      os << ":" << port;
+    }
+    os << "]";
+    if((next = next->ai_next))
+      os << ", ";
+  }
+  os << '}';
+  return os;
+}
+
+void AddrInfoCallback(void *data, int status, int timeouts,
+                      struct ares_addrinfo *ai) {
   EXPECT_NE(nullptr, data);
-  AIResult* result = reinterpret_cast<AIResult*>(data);
-  result->done = true;
-  result->status = status;
-  result->airesult = res;
-  //if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
+  AddrInfoResult* result = reinterpret_cast<AddrInfoResult*>(data);
+  result->done_ = true;
+  result->status_ = status;
+  result->timeouts_= timeouts;
+  result->ai_ = AddrInfo(ai);
+  if (verbose) std::cerr << "AddrInfoCallback(" << *result << ")" << std::endl;
 }
 
 std::ostream& operator<<(std::ostream& os, const SearchResult& result) {
diff --git a/test/ares-test.h b/test/ares-test.h
index 03e15ec..ae675aa 100644
--- a/test/ares-test.h
+++ b/test/ares-test.h
@@ -279,6 +279,30 @@ struct NameInfoResult {
 };
 std::ostream& operator<<(std::ostream& os, const NameInfoResult& result);
 
+struct AddrInfoDeleter {
+  void operator() (ares_addrinfo *ptr) {
+    if (ptr) ares_freeaddrinfo(ptr);
+  }
+};
+
+// C++ wrapper for struct ares_addrinfo.
+using AddrInfo = std::unique_ptr<ares_addrinfo, AddrInfoDeleter>;
+
+std::ostream& operator<<(std::ostream& os, const AddrInfo& result);
+
+// Structure that describes the result of an ares_addrinfo_callback invocation.
+struct AddrInfoResult {
+  AddrInfoResult() : done_(false), status_(-1), timeouts_(0) {}
+  // Whether the callback has been invoked.
+  bool done_;
+  // Explicitly provided result information.
+  int status_;
+  int timeouts_;
+  // Contents of the ares_addrinfo structure, if provided.
+  AddrInfo ai_;
+};
+std::ostream& operator<<(std::ostream& os, const AddrInfoResult& result);
+
 // Standard implementation of ares callbacks that fill out the corresponding
 // structures.
 void HostCallback(void *data, int status, int timeouts,
@@ -287,8 +311,8 @@ void SearchCallback(void *data, int status, int timeouts,
                     unsigned char *abuf, int alen);
 void NameInfoCallback(void *data, int status, int timeouts,
                       char *node, char *service);
-void AICallback(void *data, int status,
-                struct ares_addrinfo *res);
+void AddrInfoCallback(void *data, int status, int timeouts,
+                      struct ares_addrinfo *res);
 
 // Retrieve the name servers used by a channel.
 std::vector<std::string> GetNameServers(ares_channel channel);
-- 
2.16.4

