about summary refs log tree commit diff
path: root/sysdeps
diff options
context:
space:
mode:
Diffstat (limited to 'sysdeps')
-rw-r--r--sysdeps/posix/getaddrinfo.c47
1 files changed, 41 insertions, 6 deletions
diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
index adb3c4f96a..b668936095 100644
--- a/sysdeps/posix/getaddrinfo.c
+++ b/sysdeps/posix/getaddrinfo.c
@@ -1944,6 +1944,9 @@ getaddrinfo (const char *name, const char *service,
       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;
@@ -1968,7 +1971,21 @@ getaddrinfo (const char *name, const char *service,
 		 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 (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
@@ -1979,9 +1996,9 @@ getaddrinfo (const char *name, const char *service,
 		  results[i].source_addr_len = sl;
 		  results[i].got_source_addr = true;
 
-		  if (q->ai_family == PF_INET6 && in6ai != NULL)
+		  if (q->ai_family == AF_INET6 && in6ai != NULL)
 		    {
-		      /* See whether the source address is the list of
+		      /* See whether the source address is on the list of
 			 deprecated or temporary addresses.  */
 		      struct in6addrinfo tmp;
 		      struct sockaddr_in6 *sin6p
@@ -1994,14 +2011,29 @@ getaddrinfo (const char *name, const char *service,
 		      if (found != NULL)
 			results[i].source_addr_flags = found->flags;
 		    }
+		  else 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));
+		      memcpy (&sin->sin_addr,
+			      &sin6->sin6_addr.s6_addr32[3], INADDRSZ);
+		      results[i].source_addr_len = INADDRSZ;
+		      sin->sin_family = AF_INET;
+		    }
 		}
+	      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.  */
@@ -2013,6 +2045,9 @@ 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);