about summary refs log tree commit diff
path: root/sysdeps/posix/getaddrinfo.c
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps/posix/getaddrinfo.c')
-rw-r--r--sysdeps/posix/getaddrinfo.c201
1 files changed, 185 insertions, 16 deletions
diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
index b936b252fe..c9553849fd 100644
--- a/sysdeps/posix/getaddrinfo.c
+++ b/sysdeps/posix/getaddrinfo.c
@@ -54,6 +54,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <net/if.h>
 #include <nsswitch.h>
 #include <not-cancel.h>
+#include <nscd/nscd-client.h>
 
 #ifdef HAVE_LIBIDN
 extern int __idna_to_ascii_lz (const char *input, char **output, int flags);
@@ -586,10 +587,156 @@ gaih_inet (const char *name, const struct gaih_service *service,
 	  enum nss_status inet6_status = NSS_STATUS_UNAVAIL;
 	  enum nss_status status = NSS_STATUS_UNAVAIL;
 	  int no_more;
-	  nss_gethostbyname2_r fct;
 	  int old_res_options;
-	  size_t tmpbuflen = 512;
-	  char *tmpbuf = alloca (tmpbuflen);
+
+	  /* 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)
+	    {
+	      int family = req->ai_family;
+	      size_t tmpbuflen = 512;
+	      char *tmpbuf = alloca (tmpbuflen);
+	      int rc;
+	      struct hostent th;
+	      struct hostent *h;
+	      int herrno;
+
+	    simple_again:
+	      while (1)
+		{
+		  rc = __gethostbyname2_r (name, family, &th, tmpbuf,
+					   tmpbuflen, &h, &herrno);
+		  if (rc != ERANGE || herrno != NETDB_INTERNAL)
+		    break;
+		  tmpbuf = extend_alloca (tmpbuf, tmpbuflen, 2 * tmpbuflen);
+		}
+
+	      if (rc == 0)
+		{
+		  if (h == NULL)
+		    {
+		      if (req->ai_family == AF_INET6
+			  && (req->ai_flags & AI_V4MAPPED)
+			  && family == AF_INET6)
+			{
+			  /* Try again, this time looking for IPv4
+			     addresses.  */
+			  family = AF_INET;
+			  goto simple_again;
+			}
+		    }
+		  else
+		    {
+		      /* We found data, now convert it into the list.  */
+		      for (int i = 0; h->h_addr_list[i]; ++i)
+			{
+			  if (*pat == NULL)
+			    {
+			      *pat = __alloca (sizeof (struct gaih_addrtuple));
+			      (*pat)->scopeid = 0;
+			    }
+			  (*pat)->next = NULL;
+			  (*pat)->family = req->ai_family;
+			  if (family == req->ai_family)
+			    memcpy ((*pat)->addr, h->h_addr_list[i],
+				    h->h_length);
+			  else
+			    {
+			      int32_t *addr = (uint32_t *) (*pat)->addr;
+			      addr[3] = *(uint32_t *) h->h_addr_list[i];
+			      addr[2] = htonl (0xffff);
+			      addr[1] = 0;
+			      addr[0] = 0;
+			    }
+			  pat = &((*pat)->next);
+			}
+		    }
+		}
+	      else
+		{
+		  if (herrno == NETDB_INTERNAL)
+		    {
+		      __set_h_errno (herrno);
+		      return -EAI_SYSTEM;
+		    }
+		  if (herrno == TRY_AGAIN)
+		    {
+		      return -EAI_AGAIN;
+		    }
+		  /* We made requests but they turned out no data.
+		     The name is known, though.  */
+		  return (GAIH_OKIFUNSPEC | -EAI_NODATA);
+		}
+
+	      goto process_list;
+	    }
+
+#ifdef USE_NSCD
+	  /* Try to use nscd.  */
+	  struct nscd_ai_result *air = NULL;
+	  int herrno;
+	  int err = __nscd_getai (name, &air, &herrno);
+	  if (air != NULL)
+	    {
+	      /* Transform into gaih_addrtuple list.  */
+	      bool added_canon = (req->ai_flags & AI_CANONNAME) == 0;
+	      char *addrs = air->addrs;
+
+	      for (int i = 0; i < air->naddrs; ++i)
+		{
+		  socklen_t size = (air->family[i] == AF_INET
+				    ? INADDRSZ : IN6ADDRSZ);
+		  if (*pat == NULL)
+		    {
+		      *pat = __alloca (sizeof (struct gaih_addrtuple));
+		      (*pat)->scopeid = 0;
+		    }
+		  uint32_t *pataddr = (*pat)->addr;
+		  (*pat)->next = NULL;
+		  if (added_canon || air->canon == NULL)
+		    (*pat)->name = NULL;
+		  else
+		    canon = (*pat)->name = strdupa (air->canon);
+
+		  if (air->family[i] == AF_INET
+		      && req->ai_family == AF_INET6
+		      && (req->ai_flags & AI_V4MAPPED))
+		    {
+		      (*pat)->family = AF_INET6;
+		      pataddr[3] = *(uint32_t *) addrs;
+		      pataddr[2] = htonl (0xffff);
+		      pataddr[1] = 0;
+		      pataddr[0] = 0;
+		      pat = &((*pat)->next);
+		      added_canon = true;
+		    }
+		  else if (req->ai_family == AF_UNSPEC
+			   || air->family[i] == req->ai_family)
+		    {
+		      (*pat)->family = air->family[i];
+		      memcpy (pataddr, addrs, size);
+		      pat = &((*pat)->next);
+		      added_canon = true;
+		      if (air->family[i] == AF_INET6)
+			got_ipv6 = true;
+		    }
+		  addrs += size;
+		}
+
+	      if (at->family == AF_UNSPEC)
+		return (GAIH_OKIFUNSPEC | -EAI_NONAME);
+
+	      goto process_list;
+	    }
+	  else if (err != 0)
+	    {
+	      if (herrno == NETDB_INTERNAL && errno == ENOMEM)
+		return -EAI_MEMORY;
+	      if (herrno == TRY_AGAIN)
+		return -EAI_AGAIN;
+	      return -EAI_SYSTEM;
+	    }
+#endif
 
 	  if (__nss_hosts_database != NULL)
 	    {
@@ -611,9 +758,13 @@ gaih_inet (const char *name, const struct gaih_service *service,
 	  old_res_options = _res.options;
 	  _res.options &= ~RES_USE_INET6;
 
+	  size_t tmpbuflen = 512;
+	  char *tmpbuf = alloca (tmpbuflen);
+
 	  while (!no_more)
 	    {
-	      fct = __nss_lookup_function (nip, "gethostbyname2_r");
+	      nss_gethostbyname2_r fct
+		= __nss_lookup_function (nip, "gethostbyname2_r");
 
 	      if (fct != NULL)
 		{
@@ -707,6 +858,7 @@ gaih_inet (const char *name, const struct gaih_service *service,
 	    }
 	}
 
+    process_list:
       if (at->family == AF_UNSPEC)
 	return (GAIH_OKIFUNSPEC | -EAI_NONAME);
     }
@@ -917,6 +1069,7 @@ struct sort_result
 {
   struct addrinfo *dest_addr;
   struct sockaddr_storage source_addr;
+  uint8_t source_addr_len;
   bool got_source_addr;
 };
 
@@ -1377,9 +1530,10 @@ getaddrinfo (const char *name, const char *service,
       /* Sort results according to RFC 3484.  */
       struct sort_result results[nresults];
       struct addrinfo *q;
+      struct addrinfo *last = NULL;
       char *canonname = NULL;
 
-      for (i = 0, q = p; q != NULL; ++i, q = q->ai_next)
+      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;
@@ -1387,18 +1541,33 @@ getaddrinfo (const char *name, const char *service,
 	  /* 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)
+	     fact.  If we just looked up the address for a different
+	     protocol, reuse the result.  */
+	  if (last != NULL && last->ai_addrlen == q->ai_addrlen
+	      && memcmp (last->ai_addr, q->ai_addr, q->ai_addrlen) == 0)
 	    {
-	      socklen_t sl = sizeof (results[i].source_addr);
-	      if (__connect (fd, q->ai_addr, q->ai_addrlen) == 0
-		  && __getsockname (fd,
-				    (struct sockaddr *) &results[i].source_addr,
-				    &sl) == 0)
-		results[i].got_source_addr = true;
-
-	      close_not_cancel_no_status (fd);
+	      memcpy (&results[i].source_addr, &results[i - 1].source_addr,
+		      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;
+	    }
+	  else
+	    {
+	      int fd = __socket (q->ai_family, SOCK_DGRAM, IPPROTO_IP);
+	      if (fd != -1)
+		{
+		  socklen_t sl = sizeof (results[i].source_addr);
+		  if (__connect (fd, q->ai_addr, q->ai_addrlen) == 0
+		      && __getsockname (fd,
+					(struct sockaddr *) &results[i].source_addr,
+					&sl) == 0)
+		    {
+		      results[i].source_addr_len = sl;
+		      results[i].got_source_addr = true;
+		    }
+
+		  close_not_cancel_no_status (fd);
+		}
 	    }
 
 	  /* Remember the canonical name.  */