]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - sysdeps/posix/getaddrinfo.c
Fix a few error cases in *name4_r lookup handling.
[thirdparty/glibc.git] / sysdeps / posix / getaddrinfo.c
index d97b95b5e03c3849211a7fc4e1944eda544c1946..62c38f69be1ac34cefb14feec7a03ba47a144f1c 100644 (file)
@@ -36,26 +36,32 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 /* This software is Copyright 1996 by Craig Metz, All Rights Reserved.  */
 
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <ifaddrs.h>
 #include <netdb.h>
+#include <nss.h>
 #include <resolv.h>
 #include <stdbool.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
 #include <arpa/inet.h>
-#include <sys/socket.h>
+#include <net/if.h>
 #include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <sys/utsname.h>
-#include <net/if.h>
+#include <unistd.h>
 #include <nsswitch.h>
+#include <bits/libc-lock.h>
 #include <not-cancel.h>
 #include <nscd/nscd-client.h>
 #include <nscd/nscd_proto.h>
+#include <resolv/res_hconf.h>
 
 #ifdef HAVE_LIBIDN
 extern int __idna_to_ascii_lz (const char *input, char **output, int flags);
@@ -68,7 +74,7 @@ extern int __idna_to_unicode_lzlz (const char *input, char **output,
 #define GAIH_EAI        ~(GAIH_OKIFUNSPEC)
 
 #ifndef UNIX_PATH_MAX
-#define UNIX_PATH_MAX  108
+# define UNIX_PATH_MAX  108
 #endif
 
 struct gaih_service
@@ -87,21 +93,14 @@ struct gaih_servtuple
 
 static const struct gaih_servtuple nullserv;
 
-struct gaih_addrtuple
-  {
-    struct gaih_addrtuple *next;
-    char *name;
-    int family;
-    uint32_t addr[4];
-    uint32_t scopeid;
-  };
 
 struct gaih_typeproto
   {
     int socktype;
     int protocol;
-    char name[4];
-    int protoflag;
+    uint8_t protoflag;
+    bool defaultflag;
+    char name[8];
   };
 
 /* Values for `protoflag'.  */
@@ -110,18 +109,29 @@ struct gaih_typeproto
 
 static const struct gaih_typeproto gaih_inet_typeproto[] =
 {
-  { 0, 0, "", 0 },
-  { SOCK_STREAM, IPPROTO_TCP, "tcp", 0 },
-  { SOCK_DGRAM, IPPROTO_UDP, "udp", 0 },
-  { SOCK_RAW, 0, "raw", GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE },
-  { 0, 0, "", 0 }
+  { 0, 0, 0, false, "" },
+  { SOCK_STREAM, IPPROTO_TCP, 0, true, "tcp" },
+  { SOCK_DGRAM, IPPROTO_UDP, 0, true, "udp" },
+#if defined SOCK_DCCP && defined IPPROTO_DCCP
+  { SOCK_DCCP, IPPROTO_DCCP, 0, false, "dccp" },
+#endif
+#ifdef IPPROTO_UDPLITE
+  { SOCK_DGRAM, IPPROTO_UDPLITE, 0, false, "udplite" },
+#endif
+#ifdef IPPROTO_SCTP
+  { SOCK_STREAM, IPPROTO_SCTP, 0, false, "sctp" },
+  { SOCK_SEQPACKET, IPPROTO_SCTP, 0, false, "sctp" },
+#endif
+  { SOCK_RAW, 0, GAI_PROTO_PROTOANY|GAI_PROTO_NOSERVICE, true, "raw" },
+  { 0, 0, 0, false, "" }
 };
 
 struct gaih
   {
     int family;
     int (*gaih)(const char *name, const struct gaih_service *service,
-               const struct addrinfo *req, struct addrinfo **pai);
+               const struct addrinfo *req, struct addrinfo **pai,
+               unsigned int *naddrs);
   };
 
 static const struct addrinfo default_hints =
@@ -137,120 +147,9 @@ static const struct addrinfo default_hints =
   };
 
 
-#if 0
-/* Using Unix sockets this way is a security risk.  */
-static int
-gaih_local (const char *name, const struct gaih_service *service,
-           const struct addrinfo *req, struct addrinfo **pai)
-{
-  struct utsname utsname;
-
-  if ((name != NULL) && (req->ai_flags & AI_NUMERICHOST))
-    return GAIH_OKIFUNSPEC | -EAI_NONAME;
-
-  if ((name != NULL) || (req->ai_flags & AI_CANONNAME))
-    if (uname (&utsname) < 0)
-      return -EAI_SYSTEM;
-
-  if (name != NULL)
-    {
-      if (strcmp(name, "localhost") &&
-         strcmp(name, "local") &&
-         strcmp(name, "unix") &&
-         strcmp(name, utsname.nodename))
-       return GAIH_OKIFUNSPEC | -EAI_NONAME;
-    }
-
-  if (req->ai_protocol || req->ai_socktype)
-    {
-      const struct gaih_typeproto *tp = gaih_inet_typeproto + 1;
-
-      while (tp->name[0]
-            && ((tp->protoflag & GAI_PROTO_NOSERVICE) != 0
-                || (req->ai_socktype != 0 && req->ai_socktype != tp->socktype)
-                || (req->ai_protocol != 0
-                    && !(tp->protoflag & GAI_PROTO_PROTOANY)
-                    && req->ai_protocol != tp->protocol)))
-       ++tp;
-
-      if (! tp->name[0])
-       {
-         if (req->ai_socktype)
-           return (GAIH_OKIFUNSPEC | -EAI_SOCKTYPE);
-         else
-           return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
-       }
-    }
-
-  *pai = malloc (sizeof (struct addrinfo) + sizeof (struct sockaddr_un)
-                + ((req->ai_flags & AI_CANONNAME)
-                   ? (strlen(utsname.nodename) + 1): 0));
-  if (*pai == NULL)
-    return -EAI_MEMORY;
-
-  (*pai)->ai_next = NULL;
-  (*pai)->ai_flags = req->ai_flags;
-  (*pai)->ai_family = AF_LOCAL;
-  (*pai)->ai_socktype = req->ai_socktype ? req->ai_socktype : SOCK_STREAM;
-  (*pai)->ai_protocol = req->ai_protocol;
-  (*pai)->ai_addrlen = sizeof (struct sockaddr_un);
-  (*pai)->ai_addr = (void *) (*pai) + sizeof (struct addrinfo);
-
-#ifdef _HAVE_SA_LEN
-  ((struct sockaddr_un *) (*pai)->ai_addr)->sun_len =
-    sizeof (struct sockaddr_un);
-#endif /* _HAVE_SA_LEN */
-
-  ((struct sockaddr_un *)(*pai)->ai_addr)->sun_family = AF_LOCAL;
-  memset(((struct sockaddr_un *)(*pai)->ai_addr)->sun_path, 0, UNIX_PATH_MAX);
-
-  if (service)
-    {
-      struct sockaddr_un *sunp = (struct sockaddr_un *) (*pai)->ai_addr;
-
-      if (strchr (service->name, '/') != NULL)
-       {
-         if (strlen (service->name) >= sizeof (sunp->sun_path))
-           return GAIH_OKIFUNSPEC | -EAI_SERVICE;
-
-         strcpy (sunp->sun_path, service->name);
-       }
-      else
-       {
-         if (strlen (P_tmpdir "/") + 1 + strlen (service->name) >=
-             sizeof (sunp->sun_path))
-           return GAIH_OKIFUNSPEC | -EAI_SERVICE;
-
-         __stpcpy (__stpcpy (sunp->sun_path, P_tmpdir "/"), service->name);
-       }
-    }
-  else
-    {
-      /* This is a dangerous use of the interface since there is a time
-        window between the test for the file and the actual creation
-        (done by the caller) in which a file with the same name could
-        be created.  */
-      char *buf = ((struct sockaddr_un *) (*pai)->ai_addr)->sun_path;
-
-      if (__builtin_expect (__path_search (buf, L_tmpnam, NULL, NULL, 0),
-                           0) != 0
-         || __builtin_expect (__gen_tempname (buf, __GT_NOCREATE), 0) != 0)
-       return -EAI_SYSTEM;
-    }
-
-  if (req->ai_flags & AI_CANONNAME)
-    (*pai)->ai_canonname = strcpy ((char *) *pai + sizeof (struct addrinfo)
-                                  + sizeof (struct sockaddr_un),
-                                  utsname.nodename);
-  else
-    (*pai)->ai_canonname = NULL;
-  return 0;
-}
-#endif /* 0 */
-
 static int
 gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
-              const struct addrinfo *req, struct gaih_servtuple *st)
+               const struct addrinfo *req, struct gaih_servtuple *st)
 {
   struct servent *s;
   size_t tmpbuflen = 1024;
@@ -308,6 +207,7 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
       if (herrno == NETDB_INTERNAL)                                          \
        {                                                                     \
          __set_h_errno (herrno);                                             \
+         _res.options = old_res_options;                                     \
          return -EAI_SYSTEM;                                                 \
        }                                                                     \
       if (herrno == TRY_AGAIN)                                               \
@@ -352,6 +252,10 @@ gaih_inet_serv (const char *servicename, const struct gaih_typeproto *tp,
  }
 
 
+typedef enum nss_status (*nss_gethostbyname4_r)
+  (const char *name, struct gaih_addrtuple **pat,
+   char *buffer, size_t buflen, int *errnop,
+   int *h_errnop, int32_t *ttlp);
 typedef enum nss_status (*nss_gethostbyname3_r)
   (const char *name, int af, struct hostent *host,
    char *buffer, size_t buflen, int *errnop,
@@ -361,9 +265,11 @@ typedef enum nss_status (*nss_getcanonname_r)
    int *errnop, int *h_errnop);
 extern service_user *__nss_hosts_database attribute_hidden;
 
+
 static int
 gaih_inet (const char *name, const struct gaih_service *service,
-          const struct addrinfo *req, struct addrinfo **pai)
+          const struct addrinfo *req, struct addrinfo **pai,
+          unsigned int *naddrs)
 {
   const struct gaih_typeproto *tp = gaih_inet_typeproto;
   struct gaih_servtuple *st = (struct gaih_servtuple *) &nullserv;
@@ -387,16 +293,17 @@ gaih_inet (const char *name, const struct gaih_service *service,
       if (! tp->name[0])
        {
          if (req->ai_socktype)
-           return (GAIH_OKIFUNSPEC | -EAI_SOCKTYPE);
+           return GAIH_OKIFUNSPEC | -EAI_SOCKTYPE;
          else
-           return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
+           return GAIH_OKIFUNSPEC | -EAI_SERVICE;
        }
     }
 
+  int port = 0;
   if (service != NULL)
     {
       if ((tp->protoflag & GAI_PROTO_NOSERVICE) != 0)
-       return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
+       return GAIH_OKIFUNSPEC | -EAI_SERVICE;
 
       if (service->num < 0)
        {
@@ -440,68 +347,47 @@ gaih_inet (const char *name, const struct gaih_service *service,
                  pst = &(newp->next);
                }
              if (st == (struct gaih_servtuple *) &nullserv)
-               return (GAIH_OKIFUNSPEC | -EAI_SERVICE);
+               return GAIH_OKIFUNSPEC | -EAI_SERVICE;
            }
        }
       else
        {
-         if (req->ai_socktype || req->ai_protocol)
-           {
-             st = __alloca (sizeof (struct gaih_servtuple));
-             st->next = NULL;
-             st->socktype = tp->socktype;
-             st->protocol = ((tp->protoflag & GAI_PROTO_PROTOANY)
-                             ? req->ai_protocol : tp->protocol);
-             st->port = htons (service->num);
-           }
-         else
-           {
-             /* Neither socket type nor protocol is set.  Return all
-                socket types we know about.  */
-             struct gaih_servtuple **lastp = &st;
-             for (tp = gaih_inet_typeproto + 1; tp->name[0]; ++tp)
-               if ((tp->protoflag & GAI_PROTO_NOSERVICE) == 0)
-                 {
-                   struct gaih_servtuple *newp;
-
-                   newp = __alloca (sizeof (struct gaih_servtuple));
-                   newp->next = NULL;
-                   newp->socktype = tp->socktype;
-                   newp->protocol = tp->protocol;
-                   newp->port = htons (service->num);
-
-                   *lastp = newp;
-                   lastp = &newp->next;
-                 }
-           }
+         port = htons (service->num);
+         goto got_port;
        }
     }
-  else if (req->ai_socktype || req->ai_protocol)
-    {
-      st = __alloca (sizeof (struct gaih_servtuple));
-      st->next = NULL;
-      st->socktype = tp->socktype;
-      st->protocol = ((tp->protoflag & GAI_PROTO_PROTOANY)
-                     ? req->ai_protocol : tp->protocol);
-      st->port = 0;
-    }
   else
     {
-      /* Neither socket type nor protocol is set.  Return all socket types
-        we know about.  */
-      struct gaih_servtuple **lastp = &st;
-      for (++tp; tp->name[0]; ++tp)
+    got_port:
+
+      if (req->ai_socktype || req->ai_protocol)
+       {
+         st = __alloca (sizeof (struct gaih_servtuple));
+         st->next = NULL;
+         st->socktype = tp->socktype;
+         st->protocol = ((tp->protoflag & GAI_PROTO_PROTOANY)
+                         ? req->ai_protocol : tp->protocol);
+         st->port = port;
+       }
+      else
        {
-         struct gaih_servtuple *newp;
+         /* Neither socket type nor protocol is set.  Return all socket types
+            we know about.  */
+         struct gaih_servtuple **lastp = &st;
+         for (++tp; tp->name[0]; ++tp)
+           if (tp->defaultflag)
+             {
+               struct gaih_servtuple *newp;
 
-         newp = __alloca (sizeof (struct gaih_servtuple));
-         newp->next = NULL;
-         newp->socktype = tp->socktype;
-         newp->protocol = tp->protocol;
-         newp->port = 0;
+               newp = __alloca (sizeof (struct gaih_servtuple));
+               newp->next = NULL;
+               newp->socktype = tp->socktype;
+               newp->protocol = tp->protocol;
+               newp->port = port;
 
-         *lastp = newp;
-         lastp = &newp->next;
+               *lastp = newp;
+               lastp = &newp->next;
+             }
        }
     }
 
@@ -546,7 +432,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
        {
          if (req->ai_family == AF_UNSPEC || req->ai_family == AF_INET)
            at->family = AF_INET;
-         else if (req->ai_family == AF_INET6 && req->ai_flags & AI_V4MAPPED)
+         else if (req->ai_family == AF_INET6 && (req->ai_flags & AI_V4MAPPED))
            {
              at->addr[3] = at->addr[0];
              at->addr[2] = htonl (0xffff);
@@ -557,23 +443,19 @@ gaih_inet (const char *name, const struct gaih_service *service,
          else
            return -EAI_ADDRFAMILY;
 
-       dupname:
          if (req->ai_flags & AI_CANONNAME)
-           {
-             canon = strdup (name);
-             if (canon == NULL)
-               return -EAI_MEMORY;
-           }
+           canon = name;
        }
-
-      if (at->family == AF_UNSPEC)
+      else if (at->family == AF_UNSPEC)
        {
-         char *namebuf = strdupa (name);
-         char *scope_delim;
+         char *namebuf = (char *) name;
+         char *scope_delim = strchr (name, SCOPE_DELIMITER);
 
-         scope_delim = strchr (namebuf, SCOPE_DELIMITER);
-         if (scope_delim != NULL)
-           *scope_delim = '\0';
+         if (__builtin_expect (scope_delim != NULL, 0))
+           {
+             namebuf = alloca (scope_delim - name + 1);
+             *((char *) __mempcpy (namebuf, name, scope_delim - name)) = '\0';
+           }
 
          if (inet_pton (AF_INET6, namebuf, at->addr) > 0)
            {
@@ -612,7 +494,8 @@ gaih_inet (const char *name, const struct gaih_service *service,
                    }
                }
 
-             goto dupname;
+             if (req->ai_flags & AI_CANONNAME)
+               canon = name;
            }
        }
 
@@ -629,7 +512,10 @@ gaih_inet (const char *name, const struct gaih_service *service,
 
          /* If we do not have to look for IPv4 and IPv6 together, use
             the simple, old functions.  */
-         if (req->ai_family == AF_INET || req->ai_family == AF_INET6)
+         if (req->ai_family == AF_INET
+             || (req->ai_family == AF_INET6
+                 && ((req->ai_flags & AI_V4MAPPED) == 0
+                     || (req->ai_flags & AI_ALL) == 0)))
            {
              int family = req->ai_family;
              size_t tmpbuflen = 512;
@@ -703,7 +589,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
                    }
                  /* We made requests but they turned out no data.
                     The name is known, though.  */
-                 return (GAIH_OKIFUNSPEC | -EAI_NODATA);
+                 return GAIH_OKIFUNSPEC | -EAI_NODATA;
                }
 
              goto process_list;
@@ -770,11 +656,14 @@ gaih_inet (const char *name, const struct gaih_service *service,
                  free (air);
 
                  if (at->family == AF_UNSPEC)
-                   return (GAIH_OKIFUNSPEC | -EAI_NONAME);
+                   return GAIH_OKIFUNSPEC | -EAI_NONAME;
 
                  goto process_list;
                }
-             else if (err != 0 && __nss_not_use_nscd_hosts == 0)
+             else if (err == 0)
+               /* The database contains a negative entry.  */
+               return 0;
+             else if (__nss_not_use_nscd_hosts == 0)
                {
                  if (herrno == NETDB_INTERNAL && errno == ENOMEM)
                    return -EAI_MEMORY;
@@ -795,6 +684,9 @@ gaih_inet (const char *name, const struct gaih_service *service,
                                             "dns [!UNAVAIL=return] files",
                                             &nip);
 
+         /* Initialize configurations.  */
+         if (__builtin_expect (!_res_hconf.initialized, 0))
+           _res_hconf_init ();
          if (__res_maybe_init (&_res, 0) == -1)
            no_more = 1;
 
@@ -805,92 +697,139 @@ gaih_inet (const char *name, const struct gaih_service *service,
          old_res_options = _res.options;
          _res.options &= ~RES_USE_INET6;
 
-         size_t tmpbuflen = 512;
+         size_t tmpbuflen = 1024;
          char *tmpbuf = alloca (tmpbuflen);
 
          while (!no_more)
            {
-             nss_gethostbyname3_r fct = NULL;
-             if (req->ai_flags & AI_CANONNAME)
-               /* No need to use this function if we do not look for
-                  the canonical name.  The function does not exist in
-                  all NSS modules and therefore the lookup would
-                  often fail.  */
-               fct = __nss_lookup_function (nip, "gethostbyname3_r");
-             if (fct == NULL)
-               /* We are cheating here.  The gethostbyname2_r function does
-                  not have the same interface as gethostbyname3_r but the
-                  extra arguments the latter takes are added at the end.
-                  So the gethostbyname2_r code will just ignore them.  */
-               fct = __nss_lookup_function (nip, "gethostbyname2_r");
-
-             if (fct != NULL)
+             nss_gethostbyname4_r fct4
+               = __nss_lookup_function (nip, "gethostbyname4_r");
+             if (fct4 != NULL)
                {
-                 if (req->ai_family == AF_INET6
-                     || req->ai_family == AF_UNSPEC)
+                 int herrno;
+
+                 while (1)
                    {
-                     gethosts (AF_INET6, struct in6_addr);
-                     no_inet6_data = no_data;
-                     inet6_status = status;
+                     rc = 0;
+                     status = DL_CALL_FCT (fct4, (name, pat, tmpbuf,
+                                                  tmpbuflen, &rc, &herrno,
+                                                  NULL));
+                     if (status == NSS_STATUS_SUCCESS)
+                       break;
+                     if (status != NSS_STATUS_TRYAGAIN
+                         || rc != ERANGE || herrno != NETDB_INTERNAL)
+                       {
+                         if (status == NSS_STATUS_TRYAGAIN
+                             && herrno == TRY_AGAIN)
+                           no_data = EAI_AGAIN;
+                         else
+                           no_data = herrno == NO_DATA;
+                         break;
+                       }
+                     tmpbuf = extend_alloca (tmpbuf,
+                                             tmpbuflen, 2 * tmpbuflen);
                    }
-                 if (req->ai_family == AF_INET
-                     || req->ai_family == AF_UNSPEC
-                     || (req->ai_family == AF_INET6
-                         && (req->ai_flags & AI_V4MAPPED)
-                         /* Avoid generating the mapped addresses if we
-                            know we are not going to need them.  */
-                         && ((req->ai_flags & AI_ALL) || !got_ipv6)))
+
+                 no_inet6_data = no_data;
+
+                 if (status == NSS_STATUS_SUCCESS)
                    {
-                     gethosts (AF_INET, struct in_addr);
+                     if ((req->ai_flags & AI_CANONNAME) != 0 && canon == NULL)
+                       canon = (*pat)->name;
 
-                     if (req->ai_family == AF_INET)
+                     while (*pat != NULL)
+                       pat = &((*pat)->next);
+                   }
+               }
+             else
+               {
+                 nss_gethostbyname3_r fct = NULL;
+                 if (req->ai_flags & AI_CANONNAME)
+                   /* No need to use this function if we do not look for
+                      the canonical name.  The function does not exist in
+                      all NSS modules and therefore the lookup would
+                      often fail.  */
+                   fct = __nss_lookup_function (nip, "gethostbyname3_r");
+                 if (fct == NULL)
+                   /* We are cheating here.  The gethostbyname2_r
+                      function does not have the same interface as
+                      gethostbyname3_r but the extra arguments the
+                      latter takes are added at the end.  So the
+                      gethostbyname2_r code will just ignore them.  */
+                   fct = __nss_lookup_function (nip, "gethostbyname2_r");
+
+                 if (fct != NULL)
+                   {
+                     if (req->ai_family == AF_INET6
+                         || req->ai_family == AF_UNSPEC)
                        {
+                         gethosts (AF_INET6, struct in6_addr);
                          no_inet6_data = no_data;
                          inet6_status = status;
                        }
-                   }
-
-                 /* If we found one address for AF_INET or AF_INET6,
-                    don't continue the search.  */
-                 if (inet6_status == NSS_STATUS_SUCCESS
-                     || status == NSS_STATUS_SUCCESS)
-                   {
-                     if ((req->ai_flags & AI_CANONNAME) != 0 && canon == NULL)
+                     if (req->ai_family == AF_INET
+                         || req->ai_family == AF_UNSPEC
+                         || (req->ai_family == AF_INET6
+                             && (req->ai_flags & AI_V4MAPPED)
+                             /* Avoid generating the mapped addresses if we
+                                know we are not going to need them.  */
+                             && ((req->ai_flags & AI_ALL) || !got_ipv6)))
                        {
-                         /* If we need the canonical name, get it
-                            from the same service as the result.  */
-                         nss_getcanonname_r cfct;
-                         int herrno;
+                         gethosts (AF_INET, struct in_addr);
 
-                         cfct = __nss_lookup_function (nip, "getcanonname_r");
-                         if (cfct != NULL)
+                         if (req->ai_family == AF_INET)
                            {
-                             const size_t max_fqdn_len = 256;
-                             char *buf = alloca (max_fqdn_len);
-                             char *s;
-
-                             if (DL_CALL_FCT (cfct, (at->name ?: name, buf,
-                                                     max_fqdn_len, &s, &rc,
-                                                     &herrno))
-                                 == NSS_STATUS_SUCCESS)
-                               canon = s;
-                             else
-                               /* Set to name now to avoid using
-                                  gethostbyaddr.  */
-                               canon = name;
+                             no_inet6_data = no_data;
+                             inet6_status = status;
                            }
                        }
 
-                     break;
-                   }
+                     /* If we found one address for AF_INET or AF_INET6,
+                        don't continue the search.  */
+                     if (inet6_status == NSS_STATUS_SUCCESS
+                         || status == NSS_STATUS_SUCCESS)
+                       {
+                         if ((req->ai_flags & AI_CANONNAME) != 0
+                             && canon == NULL)
+                           {
+                             /* If we need the canonical name, get it
+                                from the same service as the result.  */
+                             nss_getcanonname_r cfct;
+                             int herrno;
+
+                             cfct = __nss_lookup_function (nip,
+                                                           "getcanonname_r");
+                             if (cfct != NULL)
+                               {
+                                 const size_t max_fqdn_len = 256;
+                                 char *buf = alloca (max_fqdn_len);
+                                 char *s;
+
+                                 if (DL_CALL_FCT (cfct, (at->name ?: name,
+                                                         buf, max_fqdn_len,
+                                                         &s, &rc, &herrno))
+                                     == NSS_STATUS_SUCCESS)
+                                   canon = s;
+                                 else
+                                   /* Set to name now to avoid using
+                                      gethostbyaddr.  */
+                                   canon = name;
+                               }
+                           }
+
+                         break;
+                       }
 
-                 /* We can have different states for AF_INET and
-                    AF_INET6.  Try to find a useful one for both.  */
-                 if (inet6_status == NSS_STATUS_TRYAGAIN)
-                   status = NSS_STATUS_TRYAGAIN;
-                 else if (status == NSS_STATUS_UNAVAIL &&
-                          inet6_status != NSS_STATUS_UNAVAIL)
-                   status = inet6_status;
+                     /* We can have different states for AF_INET and
+                        AF_INET6.  Try to find a useful one for both.  */
+                     if (inet6_status == NSS_STATUS_TRYAGAIN)
+                       status = NSS_STATUS_TRYAGAIN;
+                     else if (status == NSS_STATUS_UNAVAIL
+                              && inet6_status != NSS_STATUS_UNAVAIL)
+                       status = inet6_status;
+                   }
+                 else
+                   status = NSS_STATUS_UNAVAIL;
                }
 
              if (nss_next_action (nip, status) == NSS_ACTION_RETURN)
@@ -912,13 +851,13 @@ gaih_inet (const char *name, const struct gaih_service *service,
 
              /* We made requests but they turned out no data.  The name
                 is known, though.  */
-             return (GAIH_OKIFUNSPEC | -EAI_NODATA);
+             return GAIH_OKIFUNSPEC | -EAI_NODATA;
            }
        }
 
     process_list:
       if (at->family == AF_UNSPEC)
-       return (GAIH_OKIFUNSPEC | -EAI_NONAME);
+       return GAIH_OKIFUNSPEC | -EAI_NONAME;
     }
   else
     {
@@ -948,9 +887,6 @@ gaih_inet (const char *name, const struct gaih_service *service,
        }
     }
 
-  if (pai == NULL)
-    return 0;
-
   {
     struct gaih_servtuple *st2;
     struct gaih_addrtuple *at2 = at;
@@ -1022,7 +958,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
                    return -EAI_IDN_ENCODE;
                  }
                /* In case the output string is the same as the input
-                  string no new string has been allocated.  Otherwise
+                  string no new string has been allocated and we
                   make a copy.  */
                if (out == canon)
                  goto make_copy;
@@ -1039,9 +975,9 @@ gaih_inet (const char *name, const struct gaih_service *service,
              }
          }
 
-       if (at2->family == AF_INET6)
+       family = at2->family;
+       if (family == AF_INET6)
          {
-           family = AF_INET6;
            socklen = sizeof (struct sockaddr_in6);
 
            /* If we looked up IPv4 mapped address discard them here if
@@ -1053,17 +989,17 @@ gaih_inet (const char *name, const struct gaih_service *service,
              goto ignore;
          }
        else
-         {
-           family = AF_INET;
-           socklen = sizeof (struct sockaddr_in);
-         }
+         socklen = sizeof (struct sockaddr_in);
 
        for (st2 = st; st2 != NULL; st2 = st2->next)
          {
            struct addrinfo *ai;
            ai = *pai = malloc (sizeof (struct addrinfo) + socklen);
            if (ai == NULL)
-             return -EAI_MEMORY;
+             {
+               free ((char *) canon);
+               return -EAI_MEMORY;
+             }
 
            ai->ai_flags = req->ai_flags;
            ai->ai_family = family;
@@ -1081,6 +1017,10 @@ gaih_inet (const char *name, const struct gaih_service *service,
 #endif /* _HAVE_SA_LEN */
            ai->ai_addr->sa_family = family;
 
+           /* In case of an allocation error the list must be NULL
+              terminated.  */
+           ai->ai_next = NULL;
+
            if (family == AF_INET6)
              {
                struct sockaddr_in6 *sin6p =
@@ -1104,7 +1044,8 @@ gaih_inet (const char *name, const struct gaih_service *service,
 
            pai = &(ai->ai_next);
          }
-       *pai = NULL;
+
+       ++*naddrs;
 
       ignore:
        at2 = at2->next;
@@ -1113,36 +1054,73 @@ gaih_inet (const char *name, const struct gaih_service *service,
   return 0;
 }
 
-static struct gaih gaih[] =
-  {
-    { PF_INET6, gaih_inet },
-    { PF_INET, gaih_inet },
-#if 0
-    { PF_LOCAL, gaih_local },
-#endif
-    { PF_UNSPEC, NULL }
-  };
 
 struct sort_result
 {
   struct addrinfo *dest_addr;
-  struct sockaddr_storage source_addr;
+  /* Using sockaddr_storage is for now overkill.  We only support IPv4
+     and IPv6 so far.  If this changes at some point we can adjust the
+     type here.  */
+  struct sockaddr_in6 source_addr;
   uint8_t source_addr_len;
   bool got_source_addr;
+  uint8_t source_addr_flags;
+  uint8_t prefixlen;
+  uint32_t index;
+  int32_t native;
 };
 
+struct sort_result_combo
+{
+  struct sort_result *results;
+  int nresults;
+};
+
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+# define htonl_c(n) n
+#else
+# define htonl_c(n) __bswap_constant_32 (n)
+#endif
+
+static const struct scopeentry
+{
+  union
+  {
+    char addr[4];
+    uint32_t addr32;
+  };
+  uint32_t netmask;
+  int32_t scope;
+} default_scopes[] =
+  {
+    /* Link-local addresses: scope 2.  */
+    { { { 169, 254, 0, 0 } }, htonl_c (0xffff0000), 2 },
+    { { { 127, 0, 0, 0 } }, htonl_c (0xff000000), 2 },
+    /* Site-local addresses: scope 5.  */
+    { { { 10, 0, 0, 0 } }, htonl_c (0xff000000), 5 },
+    { { { 172, 16, 0, 0 } }, htonl_c (0xfff00000), 5 },
+    { { { 192, 168, 0, 0 } }, htonl_c (0xffff0000), 5 },
+    /* Default: scope 14.  */
+    { { { 0, 0, 0, 0 } }, htonl_c (0x00000000), 14 }
+  };
+
+/* The label table.  */
+static const struct scopeentry *scopes;
+
 
 static int
-get_scope (const struct sockaddr_storage *ss)
+get_scope (const struct sockaddr_in6 *in6)
 {
   int scope;
-  if (ss->ss_family == PF_INET6)
+  if (in6->sin6_family == PF_INET6)
     {
-      const struct sockaddr_in6 *in6 = (const struct sockaddr_in6 *) ss;
-
       if (! IN6_IS_ADDR_MULTICAST (&in6->sin6_addr))
        {
-         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr))
+         if (IN6_IS_ADDR_LINKLOCAL (&in6->sin6_addr)
+             /* RFC 4291 2.5.3 says that the loopback address is to be
+                treated like a link-local address.  */
+             || IN6_IS_ADDR_LOOPBACK (&in6->sin6_addr))
            scope = 2;
          else if (IN6_IS_ADDR_SITELOCAL (&in6->sin6_addr))
            scope = 5;
@@ -1153,20 +1131,20 @@ get_scope (const struct sockaddr_storage *ss)
       else
        scope = in6->sin6_addr.s6_addr[1] & 0xf;
     }
-  else if (ss->ss_family == PF_INET)
+  else if (in6->sin6_family == PF_INET)
     {
-      const struct sockaddr_in *in = (const struct sockaddr_in *) ss;
-      const uint8_t *addr = (const uint8_t *) &in->sin_addr;
-
-      /* RFC 3484 specifies how to map IPv6 addresses to scopes.
-        169.254/16 and 127/8 are link-local.  */
-      if ((addr[0] == 169 && addr[1] == 254) || addr[0] == 127)
-       scope = 2;
-      else if (addr[0] == 10 || (addr[0] == 172 && addr[1] == 16)
-              || (addr[0] == 192 && addr[1] == 168))
-       scope = 5;
-      else
-       scope = 14;
+      const struct sockaddr_in *in = (const struct sockaddr_in *) in6;
+
+      size_t cnt = 0;
+      while (1)
+       {
+         if ((in->sin_addr.s_addr & scopes[cnt].netmask)
+             == scopes[cnt].addr32)
+           return scopes[cnt].scope;
+
+         ++cnt;
+       }
+      /* NOTREACHED */
     }
   else
     /* XXX What is a good default?  */
@@ -1176,97 +1154,126 @@ get_scope (const struct sockaddr_storage *ss)
 }
 
 
-/* XXX The system administrator should be able to install other
-   tables.  We need to make this configurable.  The problem is that
-   the kernel is also involved since it needs the same table.  */
-static const struct prefixlist
+struct prefixentry
 {
   struct in6_addr prefix;
   unsigned int bits;
   int val;
-} default_labels[] =
+};
+
+
+/* The label table.  */
+static const struct prefixentry *labels;
+
+/* Default labels.  */
+static const struct prefixentry default_labels[] =
   {
     /* See RFC 3484 for the details.  */
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0001 } } },
-      128, 0 },
-    { { .in6_u = { .u6_addr16 = { 0x2002, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      16, 2 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      96, 3 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0xffff, 0x0000, 0x0000 } } },
-      96, 4 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      0, 1 }
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }
+      }, 128, 0 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 16, 2 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 3 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 4 },
+    /* The next two entries differ from RFC 3484.  We need to treat
+       IPv6 site-local addresses special because they are never NATed,
+       unlike site-locale IPv4 addresses.  If this would not happen, on
+       machines which have only IPv4 and IPv6 site-local addresses, the
+       sorting would prefer the IPv6 site-local addresses, causing
+       unnecessary delays when trying to connect to a global IPv6 address
+       through a site-local IPv6 address.  */
+    { { .__in6_u
+       = { .__u6_addr8 = { 0xfe, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 10, 5 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 7, 6 },
+    /* Additional rule for Teredo tunnels.  */
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 32, 7 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 0, 1 }
   };
 
 
-static const struct prefixlist default_precedence[] =
+/* The precedence table.  */
+static const struct prefixentry *precedence;
+
+/* The default precedences.  */
+static const struct prefixentry default_precedence[] =
   {
     /* See RFC 3484 for the details.  */
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0001 } } },
-      128, 50 },
-    { { .in6_u = { .u6_addr16 = { 0x2002, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      16, 30 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      96, 20 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0xffff, 0x0000, 0x0000 } } },
-      96, 10 },
-    { { .in6_u = { .u6_addr16 = { 0x0000, 0x0000, 0x0000, 0x0000,
-                                 0x0000, 0x0000, 0x0000, 0x0000 } } },
-      0, 40 }
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }
+      }, 128, 50 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x20, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 16, 30 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 20 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 } }
+      }, 96, 10 },
+    { { .__in6_u
+       = { .__u6_addr8 = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                           0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } }
+      }, 0, 40 }
   };
 
 
 static int
-match_prefix (const struct sockaddr_storage *ss, const struct prefixlist *list,
-             int default_val)
+match_prefix (const struct sockaddr_in6 *in6,
+             const struct prefixentry *list, int default_val)
 {
   int idx;
   struct sockaddr_in6 in6_mem;
-  const struct sockaddr_in6 *in6;
 
-  if (ss->ss_family == PF_INET6)
-    in6 = (const struct sockaddr_in6 *) ss;
-  else if (ss->ss_family == PF_INET)
+  if (in6->sin6_family == PF_INET)
     {
-      const struct sockaddr_in *in = (const struct sockaddr_in *) ss;
+      const struct sockaddr_in *in = (const struct sockaddr_in *) in6;
 
-      /* Convert to IPv6 address.  */
+      /* Construct a V4-to-6 mapped address.  */
       in6_mem.sin6_family = PF_INET6;
       in6_mem.sin6_port = in->sin_port;
       in6_mem.sin6_flowinfo = 0;
-      if (in->sin_addr.s_addr == htonl (0x7f000001))
-       in6_mem.sin6_addr = (struct in6_addr) IN6ADDR_LOOPBACK_INIT;
-      else
-       {
-         /* Construct a V4-to-6 mapped address.  */
-         memset (&in6_mem.sin6_addr, '\0', sizeof (in6_mem.sin6_addr));
-         in6_mem.sin6_addr.s6_addr16[5] = 0xffff;
-         in6_mem.sin6_addr.s6_addr32[3] = in->sin_addr.s_addr;
-         in6_mem.sin6_scope_id = 0;
-       }
+      memset (&in6_mem.sin6_addr, '\0', sizeof (in6_mem.sin6_addr));
+      in6_mem.sin6_addr.s6_addr16[5] = 0xffff;
+      in6_mem.sin6_addr.s6_addr32[3] = in->sin_addr.s_addr;
+      in6_mem.sin6_scope_id = 0;
 
       in6 = &in6_mem;
     }
-  else
+  else if (in6->sin6_family != PF_INET6)
     return default_val;
 
   for (idx = 0; ; ++idx)
     {
       unsigned int bits = list[idx].bits;
-      uint8_t *mask = list[idx].prefix.s6_addr;
-      uint8_t *val = in6->sin6_addr.s6_addr;
+      const uint8_t *mask = list[idx].prefix.s6_addr;
+      const uint8_t *val = in6->sin6_addr.s6_addr;
 
-      while (bits > 8)
+      while (bits >= 8)
        {
          if (*mask != *val)
            break;
@@ -1289,26 +1296,42 @@ match_prefix (const struct sockaddr_storage *ss, const struct prefixlist *list,
 
 
 static int
-get_label (const struct sockaddr_storage *ss)
+get_label (const struct sockaddr_in6 *in6)
 {
   /* XXX What is a good default value?  */
-  return match_prefix (ss, default_labels, INT_MAX);
+  return match_prefix (in6, labels, INT_MAX);
 }
 
 
 static int
-get_precedence (const struct sockaddr_storage *ss)
+get_precedence (const struct sockaddr_in6 *in6)
 {
   /* XXX What is a good default value?  */
-  return match_prefix (ss, default_precedence, 0);
+  return match_prefix (in6, precedence, 0);
 }
 
 
+/* Find last bit set in a word.  */
 static int
-rfc3484_sort (const void *p1, const void *p2)
+fls (uint32_t a)
 {
-  const struct sort_result *a1 = (const struct sort_result *) p1;
-  const struct sort_result *a2 = (const struct sort_result *) p2;
+  uint32_t mask;
+  int n;
+  for (n = 0, mask = 1 << 31; n < 32; mask >>= 1, ++n)
+    if ((a & mask) != 0)
+      break;
+  return n;
+}
+
+
+static int
+rfc3484_sort (const void *p1, const void *p2, void *arg)
+{
+  const size_t idx1 = *(const size_t *) p1;
+  const size_t idx2 = *(const size_t *) p2;
+  struct sort_result_combo *src = (struct sort_result_combo *) arg;
+  struct sort_result *a1 = &src->results[idx1];
+  struct sort_result *a2 = &src->results[idx2];
 
   /* Rule 1: Avoid unusable destinations.
      We have the got_source_addr flag set if the destination is reachable.  */
@@ -1321,10 +1344,10 @@ rfc3484_sort (const void *p1, const void *p2)
   /* Rule 2: Prefer matching scope.  Only interesting if both
      destination addresses are IPv6.  */
   int a1_dst_scope
-    = get_scope ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+    = get_scope ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
 
   int a2_dst_scope
-    = get_scope ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+    = get_scope ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
 
   if (a1->got_source_addr)
     {
@@ -1338,21 +1361,37 @@ rfc3484_sort (const void *p1, const void *p2)
     }
 
 
-  /* Rule 3: Avoid deprecated addresses.
-     That's something only the kernel could decide.  */
+  /* Rule 3: Avoid deprecated addresses.  */
+  if (a1->got_source_addr)
+    {
+      if (!(a1->source_addr_flags & in6ai_deprecated)
+         && (a2->source_addr_flags & in6ai_deprecated))
+       return -1;
+      if ((a1->source_addr_flags & in6ai_deprecated)
+         && !(a2->source_addr_flags & in6ai_deprecated))
+       return 1;
+    }
 
-  /* Rule 4: Prefer home addresses.
-     Another thing only the kernel can decide.  */
+  /* Rule 4: Prefer home addresses.  */
+  if (a1->got_source_addr)
+    {
+      if (!(a1->source_addr_flags & in6ai_homeaddress)
+         && (a2->source_addr_flags & in6ai_homeaddress))
+       return 1;
+      if ((a1->source_addr_flags & in6ai_homeaddress)
+         && !(a2->source_addr_flags & in6ai_homeaddress))
+       return -1;
+    }
 
   /* Rule 5: Prefer matching label.  */
   if (a1->got_source_addr)
     {
       int a1_dst_label
-       = get_label ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+       = get_label ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
       int a1_src_label = get_label (&a1->source_addr);
 
       int a2_dst_label
-       = get_label ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+       = get_label ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
       int a2_src_label = get_label (&a2->source_addr);
 
       if (a1_dst_label == a1_src_label && a2_dst_label != a2_src_label)
@@ -1364,9 +1403,9 @@ rfc3484_sort (const void *p1, const void *p2)
 
   /* Rule 6: Prefer higher precedence.  */
   int a1_prec
-    = get_precedence ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+    = get_precedence ((struct sockaddr_in6 *) a1->dest_addr->ai_addr);
   int a2_prec
-    = get_precedence ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+    = get_precedence ((struct sockaddr_in6 *) a2->dest_addr->ai_addr);
 
   if (a1_prec > a2_prec)
     return -1;
@@ -1374,8 +1413,65 @@ rfc3484_sort (const void *p1, const void *p2)
     return 1;
 
 
-  /* Rule 7: Prefer native transport.
-     XXX How to recognize tunnels?  */
+  /* Rule 7: Prefer native transport.  */
+  if (a1->got_source_addr)
+    {
+      /* The same interface index means the same interface which means
+        there is no difference in transport.  This should catch many
+        (most?) cases.  */
+      if (a1->index != a2->index)
+       {
+         int a1_native = a1->native;
+         int a2_native = a2->native;
+
+         if (a1_native == -1 || a2_native == -1)
+           {
+             uint32_t a1_index;
+             if (a1_native == -1)
+               {
+                 /* If we do not have the information use 'native' as
+                    the default.  */
+                 a1_native = 0;
+                 a1_index = a1->index;
+               }
+             else
+               a1_index = 0xffffffffu;
+
+             uint32_t a2_index;
+             if (a2_native == -1)
+               {
+                 /* If we do not have the information use 'native' as
+                    the default.  */
+                 a2_native = 0;
+                 a2_index = a2->index;
+               }
+             else
+               a2_index = 0xffffffffu;
+
+             __check_native (a1_index, &a1_native, a2_index, &a2_native);
+
+             /* Fill in the results in all the records.  */
+             for (int i = 0; i < src->nresults; ++i)
+               if (src->results[i].index == a1_index)
+                 {
+                   assert (src->results[i].native == -1
+                           || src->results[i].native == a1_native);
+                   src->results[i].native = a1_native;
+                 }
+               else if (src->results[i].index == a2_index)
+                 {
+                   assert (src->results[i].native == -1
+                           || src->results[i].native == a2_native);
+                   src->results[i].native = a2_native;
+                 }
+           }
+
+         if (a1_native && !a2_native)
+           return -1;
+         if (!a1_native && a2_native)
+           return 1;
+       }
+    }
 
 
   /* Rule 8: Prefer smaller scope.  */
@@ -1394,26 +1490,39 @@ rfc3484_sort (const void *p1, const void *p2)
 
       if (a1->dest_addr->ai_family == PF_INET)
        {
-         assert (a1->source_addr.ss_family == PF_INET);
-         assert (a2->source_addr.ss_family == PF_INET);
-
-         struct sockaddr_in *in1_dst;
-         struct sockaddr_in *in1_src;
-         struct sockaddr_in *in2_dst;
-         struct sockaddr_in *in2_src;
-
-         in1_dst = (struct sockaddr_in *) a1->dest_addr->ai_addr;
-         in1_src = (struct sockaddr_in *) &a1->source_addr;
-         in2_dst = (struct sockaddr_in *) a2->dest_addr->ai_addr;
-         in2_src = (struct sockaddr_in *) &a2->source_addr;
-
-         bit1 = ffs (in1_dst->sin_addr.s_addr ^ in1_src->sin_addr.s_addr);
-         bit2 = ffs (in2_dst->sin_addr.s_addr ^ in2_src->sin_addr.s_addr);
+         assert (a1->source_addr.sin6_family == PF_INET);
+         assert (a2->source_addr.sin6_family == PF_INET);
+
+         /* Outside of subnets, as defined by the network masks,
+            common address prefixes for IPv4 addresses make no sense.
+            So, define a non-zero value only if source and
+            destination address are on the same subnet.  */
+         struct sockaddr_in *in1_dst
+           = (struct sockaddr_in *) a1->dest_addr->ai_addr;
+         in_addr_t in1_dst_addr = ntohl (in1_dst->sin_addr.s_addr);
+         struct sockaddr_in *in1_src
+           = (struct sockaddr_in *) &a1->source_addr;
+         in_addr_t in1_src_addr = ntohl (in1_src->sin_addr.s_addr);
+         in_addr_t netmask1 = 0xffffffffu << (32 - a1->prefixlen);
+
+         if ((in1_src_addr & netmask1) == (in1_dst_addr & netmask1))
+           bit1 = fls (in1_dst_addr ^ in1_src_addr);
+
+         struct sockaddr_in *in2_dst
+           = (struct sockaddr_in *) a2->dest_addr->ai_addr;
+         in_addr_t in2_dst_addr = ntohl (in2_dst->sin_addr.s_addr);
+         struct sockaddr_in *in2_src
+           = (struct sockaddr_in *) &a2->source_addr;
+         in_addr_t in2_src_addr = ntohl (in2_src->sin_addr.s_addr);
+         in_addr_t netmask2 = 0xffffffffu << (32 - a2->prefixlen);
+
+         if ((in2_src_addr & netmask2) == (in2_dst_addr & netmask2))
+           bit2 = fls (in2_dst_addr ^ in2_src_addr);
        }
       else if (a1->dest_addr->ai_family == PF_INET6)
        {
-         assert (a1->source_addr.ss_family == PF_INET6);
-         assert (a2->source_addr.ss_family == PF_INET6);
+         assert (a1->source_addr.sin6_family == PF_INET6);
+         assert (a2->source_addr.sin6_family == PF_INET6);
 
          struct sockaddr_in6 *in1_dst;
          struct sockaddr_in6 *in1_src;
@@ -1435,10 +1544,10 @@ rfc3484_sort (const void *p1, const void *p2)
 
          if (i < 4)
            {
-             bit1 = ffs (in1_dst->sin6_addr.s6_addr32[i]
-                         ^ in1_src->sin6_addr.s6_addr32[i]);
-             bit2 = ffs (in2_dst->sin6_addr.s6_addr32[i]
-                         ^ in2_src->sin6_addr.s6_addr32[i]);
+             bit1 = fls (ntohl (in1_dst->sin6_addr.s6_addr32[i]
+                                ^ in1_src->sin6_addr.s6_addr32[i]));
+             bit2 = fls (ntohl (in2_dst->sin6_addr.s6_addr32[i]
+                                ^ in2_src->sin6_addr.s6_addr32[i]));
            }
        }
 
@@ -1449,8 +1558,503 @@ rfc3484_sort (const void *p1, const void *p2)
     }
 
 
-  /* Rule 10: Otherwise, leave the order unchanged.  */
-  return 0;
+  /* Rule 10: Otherwise, leave the order unchanged.  To ensure this
+     compare with the value indicating the order in which the entries
+     have been received from the services.  NB: no two entries can have
+     the same order so the test will never return zero.  */
+  return idx1 < idx2 ? -1 : 1;
+}
+
+
+static int
+in6aicmp (const void *p1, const void *p2)
+{
+  struct in6addrinfo *a1 = (struct in6addrinfo *) p1;
+  struct in6addrinfo *a2 = (struct in6addrinfo *) p2;
+
+  return memcmp (a1->addr, a2->addr, sizeof (a1->addr));
+}
+
+
+/* Name of the config file for RFC 3484 sorting (for now).  */
+#define GAICONF_FNAME "/etc/gai.conf"
+
+
+/* Non-zero if we are supposed to reload the config file automatically
+   whenever it changed.  */
+static int gaiconf_reload_flag;
+
+/* Non-zero if gaiconf_reload_flag was ever set to true.  */
+static int gaiconf_reload_flag_ever_set;
+
+/* Last modification time.  */
+static struct timespec gaiconf_mtime;
+
+
+libc_freeres_fn(fini)
+{
+  if (labels != default_labels)
+    {
+      const struct prefixentry *old = labels;
+      labels = default_labels;
+      free ((void *) old);
+    }
+
+  if (precedence != default_precedence)
+    {
+      const struct prefixentry *old = precedence;
+      precedence = default_precedence;
+      free ((void *) old);
+    }
+
+  if (scopes != default_scopes)
+    {
+      const struct scopeentry *old = scopes;
+      scopes = default_scopes;
+      free ((void *) old);
+    }
+}
+
+
+struct prefixlist
+{
+  struct prefixentry entry;
+  struct prefixlist *next;
+};
+
+
+struct scopelist
+{
+  struct scopeentry entry;
+  struct scopelist *next;
+};
+
+
+static void
+free_prefixlist (struct prefixlist *list)
+{
+  while (list != NULL)
+    {
+      struct prefixlist *oldp = list;
+      list = list->next;
+      free (oldp);
+    }
+}
+
+
+static void
+free_scopelist (struct scopelist *list)
+{
+  while (list != NULL)
+    {
+      struct scopelist *oldp = list;
+      list = list->next;
+      free (oldp);
+    }
+}
+
+
+static int
+prefixcmp (const void *p1, const void *p2)
+{
+  const struct prefixentry *e1 = (const struct prefixentry *) p1;
+  const struct prefixentry *e2 = (const struct prefixentry *) p2;
+
+  if (e1->bits < e2->bits)
+    return 1;
+  if (e1->bits == e2->bits)
+    return 0;
+  return -1;
+}
+
+
+static int
+scopecmp (const void *p1, const void *p2)
+{
+  const struct scopeentry *e1 = (const struct scopeentry *) p1;
+  const struct scopeentry *e2 = (const struct scopeentry *) p2;
+
+  if (e1->netmask > e2->netmask)
+    return -1;
+  if (e1->netmask == e2->netmask)
+    return 0;
+  return 1;
+}
+
+
+static void
+gaiconf_init (void)
+{
+  struct prefixlist *labellist = NULL;
+  size_t nlabellist = 0;
+  bool labellist_nullbits = false;
+  struct prefixlist *precedencelist = NULL;
+  size_t nprecedencelist = 0;
+  bool precedencelist_nullbits = false;
+  struct scopelist *scopelist =  NULL;
+  size_t nscopelist = 0;
+  bool scopelist_nullbits = false;
+
+  FILE *fp = fopen (GAICONF_FNAME, "rc");
+  if (fp != NULL)
+    {
+      struct stat64 st;
+      if (__fxstat64 (_STAT_VER, fileno (fp), &st) != 0)
+       {
+         fclose (fp);
+         goto no_file;
+       }
+
+      char *line = NULL;
+      size_t linelen = 0;
+
+      __fsetlocking (fp, FSETLOCKING_BYCALLER);
+
+      while (!feof_unlocked (fp))
+       {
+         ssize_t n = __getline (&line, &linelen, fp);
+         if (n <= 0)
+           break;
+
+         /* Handle comments.  No escaping possible so this is easy.  */
+         char *cp = strchr (line, '#');
+         if (cp != NULL)
+           *cp = '\0';
+
+         cp = line;
+         while (isspace (*cp))
+           ++cp;
+
+         char *cmd = cp;
+         while (*cp != '\0' && !isspace (*cp))
+           ++cp;
+         size_t cmdlen = cp - cmd;
+
+         if (*cp != '\0')
+           *cp++ = '\0';
+         while (isspace (*cp))
+           ++cp;
+
+         char *val1 = cp;
+         while (*cp != '\0' && !isspace (*cp))
+           ++cp;
+         size_t val1len = cp - cmd;
+
+         /* We always need at least two values.  */
+         if (val1len == 0)
+           continue;
+
+         if (*cp != '\0')
+           *cp++ = '\0';
+         while (isspace (*cp))
+           ++cp;
+
+         char *val2 = cp;
+         while (*cp != '\0' && !isspace (*cp))
+           ++cp;
+
+         /*  Ignore the rest of the line.  */
+         *cp = '\0';
+
+         struct prefixlist **listp;
+         size_t *lenp;
+         bool *nullbitsp;
+         switch (cmdlen)
+           {
+           case 5:
+             if (strcmp (cmd, "label") == 0)
+               {
+                 struct in6_addr prefix;
+                 unsigned long int bits;
+                 unsigned long int val;
+                 char *endp;
+
+                 listp = &labellist;
+                 lenp = &nlabellist;
+                 nullbitsp = &labellist_nullbits;
+
+               new_elem:
+                 bits = 128;
+                 __set_errno (0);
+                 cp = strchr (val1, '/');
+                 if (cp != NULL)
+                   *cp++ = '\0';
+                 if (inet_pton (AF_INET6, val1, &prefix)
+                     && (cp == NULL
+                         || (bits = strtoul (cp, &endp, 10)) != ULONG_MAX
+                         || errno != ERANGE)
+                     && *endp == '\0'
+                     && bits <= 128
+                     && ((val = strtoul (val2, &endp, 10)) != ULONG_MAX
+                         || errno != ERANGE)
+                     && *endp == '\0'
+                     && val <= INT_MAX)
+                   {
+                     struct prefixlist *newp = malloc (sizeof (*newp));
+                     if (newp == NULL)
+                       {
+                         free (line);
+                         fclose (fp);
+                         goto no_file;
+                       }
+
+                     memcpy (&newp->entry.prefix, &prefix, sizeof (prefix));
+                     newp->entry.bits = bits;
+                     newp->entry.val = val;
+                     newp->next = *listp;
+                     *listp = newp;
+                     ++*lenp;
+                     *nullbitsp |= bits == 0;
+                   }
+               }
+             break;
+
+           case 6:
+             if (strcmp (cmd, "reload") == 0)
+               {
+                 gaiconf_reload_flag = strcmp (val1, "yes") == 0;
+                 if (gaiconf_reload_flag)
+                   gaiconf_reload_flag_ever_set = 1;
+               }
+             break;
+
+           case 7:
+             if (strcmp (cmd, "scopev4") == 0)
+               {
+                 struct in6_addr prefix;
+                 unsigned long int bits;
+                 unsigned long int val;
+                 char *endp;
+
+                 bits = 32;
+                 __set_errno (0);
+                 cp = strchr (val1, '/');
+                 if (cp != NULL)
+                   *cp++ = '\0';
+                 if (inet_pton (AF_INET6, val1, &prefix))
+                   {
+                     bits = 128;
+                     if (IN6_IS_ADDR_V4MAPPED (&prefix)
+                         && (cp == NULL
+                             || (bits = strtoul (cp, &endp, 10)) != ULONG_MAX
+                             || errno != ERANGE)
+                         && *endp == '\0'
+                         && bits >= 96
+                         && bits <= 128
+                         && ((val = strtoul (val2, &endp, 10)) != ULONG_MAX
+                             || errno != ERANGE)
+                         && *endp == '\0'
+                         && val <= INT_MAX)
+                       {
+                         struct scopelist *newp;
+                       new_scope:
+                         newp = malloc (sizeof (*newp));
+                         if (newp == NULL)
+                           {
+                             free (line);
+                             fclose (fp);
+                             goto no_file;
+                           }
+
+                         newp->entry.netmask = htonl (bits != 96
+                                                      ? (0xffffffff
+                                                         << (128 - bits))
+                                                      : 0);
+                         newp->entry.addr32 = (prefix.s6_addr32[3]
+                                               & newp->entry.netmask);
+                         newp->entry.scope = val;
+                         newp->next = scopelist;
+                         scopelist = newp;
+                         ++nscopelist;
+                         scopelist_nullbits |= bits == 96;
+                       }
+                   }
+                 else if (inet_pton (AF_INET, val1, &prefix.s6_addr32[3])
+                          && (cp == NULL
+                              || (bits = strtoul (cp, &endp, 10)) != ULONG_MAX
+                              || errno != ERANGE)
+                          && *endp == '\0'
+                          && bits <= 32
+                          && ((val = strtoul (val2, &endp, 10)) != ULONG_MAX
+                              || errno != ERANGE)
+                          && *endp == '\0'
+                          && val <= INT_MAX)
+                   {
+                     bits += 96;
+                     goto new_scope;
+                   }
+               }
+             break;
+
+           case 10:
+             if (strcmp (cmd, "precedence") == 0)
+               {
+                 listp = &precedencelist;
+                 lenp = &nprecedencelist;
+                 nullbitsp = &precedencelist_nullbits;
+                 goto new_elem;
+               }
+             break;
+           }
+       }
+
+      free (line);
+
+      fclose (fp);
+
+      /* Create the array for the labels.  */
+      struct prefixentry *new_labels;
+      if (nlabellist > 0)
+       {
+         if (!labellist_nullbits)
+           ++nlabellist;
+         new_labels = malloc (nlabellist * sizeof (*new_labels));
+         if (new_labels == NULL)
+           goto no_file;
+
+         int i = nlabellist;
+         if (!labellist_nullbits)
+           {
+             --i;
+             memset (&new_labels[i].prefix, '\0', sizeof (struct in6_addr));
+             new_labels[i].bits = 0;
+             new_labels[i].val = 1;
+           }
+
+         struct prefixlist *l = labellist;
+         while (i-- > 0)
+           {
+             new_labels[i] = l->entry;
+             l = l->next;
+           }
+         free_prefixlist (labellist);
+
+         /* Sort the entries so that the most specific ones are at
+            the beginning.  */
+         qsort (new_labels, nlabellist, sizeof (*new_labels), prefixcmp);
+       }
+      else
+       new_labels = (struct prefixentry *) default_labels;
+
+      struct prefixentry *new_precedence;
+      if (nprecedencelist > 0)
+       {
+         if (!precedencelist_nullbits)
+           ++nprecedencelist;
+         new_precedence = malloc (nprecedencelist * sizeof (*new_precedence));
+         if (new_precedence == NULL)
+           {
+             if (new_labels != default_labels)
+               free (new_labels);
+             goto no_file;
+           }
+
+         int i = nprecedencelist;
+         if (!precedencelist_nullbits)
+           {
+             --i;
+             memset (&new_precedence[i].prefix, '\0',
+                     sizeof (struct in6_addr));
+             new_precedence[i].bits = 0;
+             new_precedence[i].val = 40;
+           }
+
+         struct prefixlist *l = precedencelist;
+         while (i-- > 0)
+           {
+             new_precedence[i] = l->entry;
+             l = l->next;
+           }
+         free_prefixlist (precedencelist);
+
+         /* Sort the entries so that the most specific ones are at
+            the beginning.  */
+         qsort (new_precedence, nprecedencelist, sizeof (*new_precedence),
+                prefixcmp);
+       }
+      else
+       new_precedence = (struct prefixentry *) default_precedence;
+
+      struct scopeentry *new_scopes;
+      if (nscopelist > 0)
+       {
+         if (!scopelist_nullbits)
+           ++nscopelist;
+         new_scopes = malloc (nscopelist * sizeof (*new_scopes));
+         if (new_scopes == NULL)
+           {
+             if (new_labels != default_labels)
+               free (new_labels);
+             if (new_precedence != default_precedence)
+               free (new_precedence);
+             goto no_file;
+           }
+
+         int i = nscopelist;
+         if (!scopelist_nullbits)
+           {
+             --i;
+             new_scopes[i].addr32 = 0;
+             new_scopes[i].netmask = 0;
+             new_scopes[i].scope = 14;
+           }
+
+         struct scopelist *l = scopelist;
+         while (i-- > 0)
+           {
+             new_scopes[i] = l->entry;
+             l = l->next;
+           }
+         free_scopelist (scopelist);
+
+         /* Sort the entries so that the most specific ones are at
+            the beginning.  */
+         qsort (new_scopes, nscopelist, sizeof (*new_scopes),
+                scopecmp);
+       }
+      else
+       new_scopes = (struct scopeentry *) default_scopes;
+
+      /* Now we are ready to replace the values.  */
+      const struct prefixentry *old = labels;
+      labels = new_labels;
+      if (old != default_labels)
+       free ((void *) old);
+
+      old = precedence;
+      precedence = new_precedence;
+      if (old != default_precedence)
+       free ((void *) old);
+
+      const struct scopeentry *oldscope = scopes;
+      scopes = new_scopes;
+      if (oldscope != default_scopes)
+       free ((void *) oldscope);
+
+      gaiconf_mtime = st.st_mtim;
+    }
+  else
+    {
+    no_file:
+      free_prefixlist (labellist);
+      free_prefixlist (precedencelist);
+      free_scopelist (scopelist);
+
+      /* If we previously read the file but it is gone now, free the
+        old data and use the builtin one.  Leave the reload flag
+        alone.  */
+      fini ();
+    }
+}
+
+
+static void
+gaiconf_reload (void)
+{
+  struct stat64 st;
+  if (__xstat64 (_STAT_VER, GAICONF_FNAME, &st) != 0
+      || memcmp (&st.st_mtim, &gaiconf_mtime, sizeof (gaiconf_mtime)) != 0)
+    gaiconf_init ();
 }
 
 
@@ -1458,10 +2062,9 @@ int
 getaddrinfo (const char *name, const char *service,
             const struct addrinfo *hints, struct addrinfo **pai)
 {
-  int i = 0, j = 0, last_i = 0;
+  int i = 0, last_i = 0;
   int nresults = 0;
-  struct addrinfo *p = NULL, **end;
-  struct gaih *g = gaih, *pg = NULL;
+  struct addrinfo *p = NULL;
   struct gaih_service gaih_service, *pservice;
   struct addrinfo local_hints;
 
@@ -1489,21 +2092,24 @@ getaddrinfo (const char *name, const char *service,
   if ((hints->ai_flags & AI_CANONNAME) && name == NULL)
     return EAI_BADFLAGS;
 
+  struct in6addrinfo *in6ai = NULL;
+  size_t in6ailen = 0;
+  bool seen_ipv4 = false;
+  bool seen_ipv6 = false;
+  /* We might need information about what interfaces are available.
+     Also determine whether we have IPv4 or IPv6 interfaces or both.  We
+     cannot cache the results since new interfaces could be added at
+     any time.  */
+  __check_pf (&seen_ipv4, &seen_ipv6, &in6ai, &in6ailen);
+
   if (hints->ai_flags & AI_ADDRCONFIG)
     {
-      /* Determine whether we have IPv4 or IPv6 interfaces or both.
-        We cannot cache the results since new interfaces could be
-        added at any time.  */
-      bool seen_ipv4;
-      bool seen_ipv6;
-      __check_pf (&seen_ipv4, &seen_ipv6);
-
       /* Now make a decision on what we return, if anything.  */
       if (hints->ai_family == PF_UNSPEC && (seen_ipv4 || seen_ipv6))
        {
          /* If we haven't seen both IPv4 and IPv6 interfaces we can
             narrow down the search.  */
-         if (! seen_ipv4 || ! seen_ipv6)
+         if ((! seen_ipv4 || ! seen_ipv6) && (seen_ipv4 || seen_ipv6))
            {
              local_hints = *hints;
              local_hints.ai_family = seen_ipv4 ? PF_INET : PF_INET6;
@@ -1512,8 +2118,11 @@ getaddrinfo (const char *name, const char *service,
        }
       else if ((hints->ai_family == PF_INET && ! seen_ipv4)
               || (hints->ai_family == PF_INET6 && ! seen_ipv6))
-       /* We cannot possibly return a valid answer.  */
-       return EAI_NONAME;
+       {
+         /* We cannot possibly return a valid answer.  */
+         free (in6ai);
+         return EAI_NONAME;
+       }
     }
 
   if (service && service[0])
@@ -1524,7 +2133,10 @@ getaddrinfo (const char *name, const char *service,
       if (*c != '\0')
        {
          if (hints->ai_flags & AI_NUMERICSERV)
-           return EAI_NONAME;
+           {
+             free (in6ai);
+             return EAI_NONAME;
+           }
 
          gaih_service.num = -1;
        }
@@ -1534,63 +2146,58 @@ getaddrinfo (const char *name, const char *service,
   else
     pservice = NULL;
 
-  if (pai)
-    end = &p;
-  else
-    end = NULL;
+  struct addrinfo **end = &p;
 
-  while (g->gaih)
+  unsigned int naddrs = 0;
+  if (hints->ai_family == AF_UNSPEC || hints->ai_family == AF_INET
+      || hints->ai_family == AF_INET6)
     {
-      if (hints->ai_family == g->family || hints->ai_family == AF_UNSPEC)
+      last_i = gaih_inet (name, pservice, hints, end, &naddrs);
+      if (last_i != 0)
        {
-         j++;
-         if (pg == NULL || pg->gaih != g->gaih)
-           {
-             pg = g;
-             i = g->gaih (name, pservice, hints, end);
-             if (i != 0)
-               {
-                 /* EAI_NODATA is a more specific result as it says that
-                    we found a result but it is not usable.  */
-                 if (last_i != (GAIH_OKIFUNSPEC | -EAI_NODATA))
-                   last_i = i;
-
-                 if (hints->ai_family == AF_UNSPEC && (i & GAIH_OKIFUNSPEC))
-                   {
-                     ++g;
-                     continue;
-                   }
+         freeaddrinfo (p);
+         free (in6ai);
 
-                 freeaddrinfo (p);
-
-                 return -(i & GAIH_EAI);
-               }
-             if (end)
-               while (*end)
-                 {
-                   end = &((*end)->ai_next);
-                   ++nresults;
-                 }
-           }
+         return -(last_i & GAIH_EAI);
+       }
+      while (*end)
+       {
+         end = &((*end)->ai_next);
+         ++nresults;
        }
-      ++g;
+    }
+  else
+    {
+      free (in6ai);
+      return EAI_FAMILY;
     }
 
-  if (j == 0)
-    return EAI_FAMILY;
-
-  if (nresults > 1)
+  if (naddrs > 1)
     {
+      /* Read the config file.  */
+      __libc_once_define (static, once);
+      __typeof (once) old_once = once;
+      __libc_once (once, gaiconf_init);
       /* Sort results according to RFC 3484.  */
       struct sort_result results[nresults];
+      size_t order[nresults];
       struct addrinfo *q;
       struct addrinfo *last = NULL;
       char *canonname = NULL;
 
+      /* If we have information about deprecated and temporary addresses
+        sort the array now.  */
+      if (in6ai != NULL)
+       qsort (in6ai, in6ailen, sizeof (*in6ai), in6aicmp);
+
+      int fd = -1;
+      int af = AF_UNSPEC;
+
       for (i = 0, q = p; q != NULL; ++i, last = q, q = q->ai_next)
        {
          results[i].dest_addr = q;
-         results[i].got_source_addr = false;
+         results[i].native = -1;
+         order[i] = i;
 
          /* If we just looked up the address for a different
             protocol, reuse the result.  */
@@ -1601,14 +2208,36 @@ getaddrinfo (const char *name, const char *service,
                      results[i - 1].source_addr_len);
              results[i].source_addr_len = results[i - 1].source_addr_len;
              results[i].got_source_addr = results[i - 1].got_source_addr;
+             results[i].source_addr_flags = results[i - 1].source_addr_flags;
+             results[i].prefixlen = results[i - 1].prefixlen;
+             results[i].index = results[i - 1].index;
            }
          else
            {
+             results[i].got_source_addr = false;
+             results[i].source_addr_flags = 0;
+             results[i].prefixlen = 0;
+             results[i].index = 0xffffffffu;
+
              /* We overwrite the type with SOCK_DGRAM since we do not
                 want connect() to connect to the other side.  If we
                 cannot determine the source address remember this
                 fact.  */
-             int fd = __socket (q->ai_family, SOCK_DGRAM, IPPROTO_IP);
+             if (fd == -1 || (af == AF_INET && q->ai_family == AF_INET6))
+               {
+                 if (fd != -1)
+                 close_retry:
+                   close_not_cancel_no_status (fd);
+                 af = q->ai_family;
+                 fd = __socket (af, SOCK_DGRAM, IPPROTO_IP);
+               }
+             else
+               {
+                 /* Reset the connection.  */
+                 struct sockaddr sa = { .sa_family = AF_UNSPEC };
+                 __connect (fd, &sa, sizeof (sa));
+               }
+
              socklen_t sl = sizeof (results[i].source_addr);
              if (fd != -1
                  && __connect (fd, q->ai_addr, q->ai_addrlen) == 0
@@ -1618,14 +2247,70 @@ getaddrinfo (const char *name, const char *service,
                {
                  results[i].source_addr_len = sl;
                  results[i].got_source_addr = true;
+
+                 if (in6ai != NULL)
+                   {
+                     /* See whether the source address is on the list of
+                        deprecated or temporary addresses.  */
+                     struct in6addrinfo tmp;
+
+                     if (q->ai_family == AF_INET && af == AF_INET)
+                       {
+                         struct sockaddr_in *sinp
+                           = (struct sockaddr_in *) &results[i].source_addr;
+                         tmp.addr[0] = 0;
+                         tmp.addr[1] = 0;
+                         tmp.addr[2] = htonl (0xffff);
+                         tmp.addr[3] = sinp->sin_addr.s_addr;
+                       }
+                     else
+                       {
+                         struct sockaddr_in6 *sin6p
+                           = (struct sockaddr_in6 *) &results[i].source_addr;
+                         memcpy (tmp.addr, &sin6p->sin6_addr, IN6ADDRSZ);
+                       }
+
+                     struct in6addrinfo *found
+                       = bsearch (&tmp, in6ai, in6ailen, sizeof (*in6ai),
+                                  in6aicmp);
+                     if (found != NULL)
+                       {
+                         results[i].source_addr_flags = found->flags;
+                         results[i].prefixlen = found->prefixlen;
+                         results[i].index = found->index;
+                       }
+                   }
+
+                 if (q->ai_family == AF_INET && af == AF_INET6)
+                   {
+                     /* We have to convert the address.  The socket is
+                        IPv6 and the request is for IPv4.  */
+                     struct sockaddr_in6 *sin6
+                       = (struct sockaddr_in6 *) &results[i].source_addr;
+                     struct sockaddr_in *sin
+                       = (struct sockaddr_in *) &results[i].source_addr;
+                     assert (IN6_IS_ADDR_V4MAPPED (sin6->sin6_addr.s6_addr32));
+                     sin->sin_family = AF_INET;
+                     /* We do not have to initialize sin_port since this
+                        fields has the same position and size in the IPv6
+                        structure.  */
+                     assert (offsetof (struct sockaddr_in, sin_port)
+                             == offsetof (struct sockaddr_in6, sin6_port));
+                     assert (sizeof (sin->sin_port)
+                             == sizeof (sin6->sin6_port));
+                     memcpy (&sin->sin_addr,
+                             &sin6->sin6_addr.s6_addr32[3], INADDRSZ);
+                     results[i].source_addr_len = sizeof (struct sockaddr_in);
+                   }
                }
+             else if (errno == EAFNOSUPPORT && af == AF_INET6
+                      && q->ai_family == AF_INET)
+               /* This could mean IPv6 sockets are IPv6-only.  */
+               goto close_retry;
              else
                /* Just make sure that if we have to process the same
                   address again we do not copy any memory.  */
                results[i].source_addr_len = 0;
-
-             if (fd != -1)
-               close_not_cancel_no_status (fd);
            }
 
          /* Remember the canonical name.  */
@@ -1637,29 +2322,44 @@ getaddrinfo (const char *name, const char *service,
            }
        }
 
+      if (fd != -1)
+       close_not_cancel_no_status (fd);
+
       /* We got all the source addresses we can get, now sort using
         the information.  */
-      qsort (results, nresults, sizeof (results[0]), rfc3484_sort);
+      struct sort_result_combo src
+       = { .results = results, .nresults = nresults };
+      if (__builtin_expect (gaiconf_reload_flag_ever_set, 0))
+       {
+         __libc_lock_define_initialized (static, lock);
+
+         __libc_lock_lock (lock);
+         if (old_once && gaiconf_reload_flag)
+           gaiconf_reload ();
+         qsort_r (order, nresults, sizeof (order[0]), rfc3484_sort, &src);
+         __libc_lock_unlock (lock);
+       }
+      else
+       qsort_r (order, nresults, sizeof (order[0]), rfc3484_sort, &src);
 
       /* Queue the results up as they come out of sorting.  */
-      q = p = results[0].dest_addr;
+      q = p = results[order[0]].dest_addr;
       for (i = 1; i < nresults; ++i)
-       q = q->ai_next = results[i].dest_addr;
+       q = q->ai_next = results[order[i]].dest_addr;
       q->ai_next = NULL;
 
       /* Fill in the canonical name into the new first entry.  */
       p->ai_canonname = canonname;
     }
 
+  free (in6ai);
+
   if (p)
     {
       *pai = p;
       return 0;
     }
 
-  if (pai == NULL && last_i == 0)
-    return 0;
-
   return last_i ? -(last_i & GAIH_EAI) : EAI_NONAME;
 }
 libc_hidden_def (getaddrinfo)