about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rwxr-xr-xnptl/sysdeps/pthread/configure40
-rw-r--r--sysdeps/posix/getaddrinfo.c378
3 files changed, 414 insertions, 9 deletions
diff --git a/ChangeLog b/ChangeLog
index 5d49a9ee0f..b4e18549f8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+2003-11-17  Ulrich Drepper  <drepper@redhat.com>
+
+	* sysdeps/posix/getaddrinfo.c: Add support for destination address
+	selection according to RFC 3484.
+
 2003-11-15  Ulrich Drepper  <drepper@redhat.com>
 
 	* posix/regex_internal.h: Add forward declaration of re_dfa_t.
diff --git a/nptl/sysdeps/pthread/configure b/nptl/sysdeps/pthread/configure
index 2241354767..7fa5348957 100755
--- a/nptl/sysdeps/pthread/configure
+++ b/nptl/sysdeps/pthread/configure
@@ -30,7 +30,6 @@ if test "${libc_cv_forced_unwind+set}" = set; then
   echo $ECHO_N "(cached) $ECHO_C" >&6
 else
   cat >conftest.$ac_ext <<_ACEOF
-#line $LINENO "configure"
 /* confdefs.h.  */
 _ACEOF
 cat confdefs.h >>conftest.$ac_ext
@@ -50,11 +49,21 @@ _Unwind_GetCFA (context)
 _ACEOF
 rm -f conftest.$ac_objext conftest$ac_exeext
 if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
-  (eval $ac_link) 2>&5
+  (eval $ac_link) 2>conftest.er1
   ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
   echo "$as_me:$LINENO: \$? = $ac_status" >&5
   (exit $ac_status); } &&
-         { ac_try='test -s conftest$ac_exeext'
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest$ac_exeext'
   { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
   (eval $ac_try) 2>&5
   ac_status=$?
@@ -67,7 +76,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
 
 libc_cv_forced_unwind=no
 fi
-rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
 fi
 echo "$as_me:$LINENO: result: $libc_cv_forced_unwind" >&5
 echo "${ECHO_T}$libc_cv_forced_unwind" >&6
@@ -84,7 +94,6 @@ if test "${libc_cv_c_cleanup+set}" = set; then
   echo $ECHO_N "(cached) $ECHO_C" >&6
 else
     cat >conftest.$ac_ext <<_ACEOF
-#line $LINENO "configure"
 /* confdefs.h.  */
 _ACEOF
 cat confdefs.h >>conftest.$ac_ext
@@ -106,11 +115,21 @@ main ()
 _ACEOF
 rm -f conftest.$ac_objext conftest$ac_exeext
 if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
-  (eval $ac_link) 2>&5
+  (eval $ac_link) 2>conftest.er1
   ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
   echo "$as_me:$LINENO: \$? = $ac_status" >&5
   (exit $ac_status); } &&
-         { ac_try='test -s conftest$ac_exeext'
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest$ac_exeext'
   { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
   (eval $ac_try) 2>&5
   ac_status=$?
@@ -123,7 +142,8 @@ sed 's/^/| /' conftest.$ac_ext >&5
 
 libc_cv_c_cleanup=no
 fi
-rm -f conftest.$ac_objext conftest$ac_exeext conftest.$ac_ext
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
 fi
 echo "$as_me:$LINENO: result: $libc_cv_c_cleanup" >&5
 echo "${ECHO_T}$libc_cv_c_cleanup" >&6
@@ -133,4 +153,8 @@ echo "${ECHO_T}$libc_cv_c_cleanup" >&6
 echo "$as_me: error: the compiler must support C cleanup handling" >&2;}
    { (exit 1); exit 1; }; }
   fi
+else
+  { { echo "$as_me:$LINENO: error: forced unwind support is required" >&5
+echo "$as_me: error: forced unwind support is required" >&2;}
+   { (exit 1); exit 1; }; }
 fi
diff --git a/sysdeps/posix/getaddrinfo.c b/sysdeps/posix/getaddrinfo.c
index 3b86b25122..4885a53118 100644
--- a/sysdeps/posix/getaddrinfo.c
+++ b/sysdeps/posix/getaddrinfo.c
@@ -53,6 +53,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #include <sys/utsname.h>
 #include <net/if.h>
 #include <nsswitch.h>
+#include <not-cancel.h>
 
 #define GAIH_OKIFUNSPEC 0x0100
 #define GAIH_EAI        ~(GAIH_OKIFUNSPEC)
@@ -894,11 +895,342 @@ static struct gaih gaih[] =
     { PF_UNSPEC, NULL }
   };
 
+struct sort_result
+{
+  struct addrinfo *dest_addr;
+  struct sockaddr_storage source_addr;
+  bool got_source_addr;
+};
+
+
+static int
+get_scope (const struct sockaddr_storage *ss)
+{
+  int scope;
+  if (ss->ss_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))
+	    scope = 2;
+	  else if (IN6_IS_ADDR_SITELOCAL (&in6->sin6_addr))
+	    scope = 5;
+	  else
+	    /* XXX Is this the correct default behavior?  */
+	    scope = 14;
+	}
+      else
+	scope = in6->sin6_addr.s6_addr[1] & 0xf;
+    }
+  else if (ss->ss_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;
+    }
+  else
+    /* XXX What is a good default?  */
+    scope = 15;
+
+  return scope;
+}
+
+
+/* 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 in6_addr prefix;
+  unsigned int bits;
+  int val;
+} 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 }
+  };
+
+
+static const struct prefixlist 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 }
+  };
+
+
+static int
+match_prefix (const struct sockaddr_storage *ss, const struct prefixlist *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)
+    {
+      const struct sockaddr_in *in = (const struct sockaddr_in *) ss;
+
+      /* Convert to IPv6 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;
+	}
+
+      in6 = &in6_mem;
+    }
+  else
+    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;
+
+      while (bits > 8)
+	{
+	  if (*mask != *val)
+	    break;
+
+	  ++mask;
+	  ++val;
+	  bits -= 8;
+	}
+
+      if (bits < 8)
+	{
+	  if ((*mask & (0xff >> bits)) == (*val & (0xff >> bits)))
+	    /* Match!  */
+	    break;
+	}
+    }
+
+  return list[idx].val;
+}
+
+
+static int
+get_label (const struct sockaddr_storage *ss)
+{
+  /* XXX What is a good default value?  */
+  return match_prefix (ss, default_labels, INT_MAX);
+}
+
+
+static int
+get_precedence (const struct sockaddr_storage *ss)
+{
+  /* XXX What is a good default value?  */
+  return match_prefix (ss, default_precedence, 0);
+}
+
+
+static int
+rfc3484_sort (const void *p1, const void *p2)
+{
+  const struct sort_result *a1 = (const struct sort_result *) p1;
+  const struct sort_result *a2 = (const struct sort_result *) p2;
+
+  /* Rule 1: Avoid unusable destinations.
+     We have the got_source_addr flag set if the destination is reachable.  */
+  if (a1->got_source_addr && ! a2->got_source_addr)
+    return -1;
+  if (! a1->got_source_addr && a2->got_source_addr)
+    return 1;
+
+
+  /* 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);
+
+  int a2_dst_scope
+    = get_scope ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+
+  if (a1->got_source_addr)
+    {
+      int a1_src_scope = get_scope (&a1->source_addr);
+      int a2_src_scope = get_scope (&a2->source_addr);
+
+      if (a1_dst_scope == a1_src_scope && a2_dst_scope != a2_src_scope)
+	return -1;
+      if (a1_dst_scope != a1_src_scope && a2_dst_scope == a2_src_scope)
+	return 1;
+    }
+
+
+  /* Rule 3: Avoid deprecated addresses.
+     That's something only the kernel could decide.  */
+
+  /* Rule 4: Prefer home addresses.
+     Another thing only the kernel can decide.  */
+
+  /* Rule 5: Prefer matching label.  */
+  if (a1->got_source_addr)
+    {
+      int a1_dst_label
+	= get_label ((struct sockaddr_storage *) 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);
+      int a2_src_label = get_label (&a2->source_addr);
+
+      if (a1_dst_label == a1_src_label && a2_dst_label != a2_src_label)
+	return -1;
+      if (a1_dst_label != a1_src_label && a2_dst_label == a2_src_label)
+	return 1;
+    }
+
+
+  /* Rule 6: Prefer higher precedence.  */
+  int a1_prec
+    = get_precedence ((struct sockaddr_storage *) a1->dest_addr->ai_addr);
+  int a2_prec
+    = get_precedence ((struct sockaddr_storage *) a2->dest_addr->ai_addr);
+
+  if (a1_prec > a2_prec)
+    return -1;
+  if (a1_prec < a2_prec)
+    return 1;
+
+
+  /* Rule 7: Prefer native transport.
+     XXX How to recognize tunnels?  */
+
+
+  /* Rule 8: Prefer smaller scope.  */
+  if (a1_dst_scope < a2_dst_scope)
+    return -1;
+  if (a1_dst_scope > a2_dst_scope)
+    return 1;
+
+
+  /* Rule 9: Use longest matching prefix.  */
+  if (a1->got_source_addr
+      && a1->dest_addr->ai_family == a2->dest_addr->ai_family)
+    {
+      int bit1 = 0;
+      int bit2 = 0;
+
+      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);
+	}
+      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);
+
+	  struct sockaddr_in6 *in1_dst;
+	  struct sockaddr_in6 *in1_src;
+	  struct sockaddr_in6 *in2_dst;
+	  struct sockaddr_in6 *in2_src;
+
+	  in1_dst = (struct sockaddr_in6 *) a1->dest_addr->ai_addr;
+	  in1_src = (struct sockaddr_in6 *) &a1->source_addr;
+	  in2_dst = (struct sockaddr_in6 *) a2->dest_addr->ai_addr;
+	  in2_src = (struct sockaddr_in6 *) &a2->source_addr;
+
+	  int i;
+	  for (i = 0; i < 4; ++i)
+	    if (in1_dst->sin6_addr.s6_addr32[i]
+		!= in1_src->sin6_addr.s6_addr32[i]
+		|| (in2_dst->sin6_addr.s6_addr32[i]
+		    != in2_src->sin6_addr.s6_addr32[i]))
+	      break;
+
+	  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]);
+	    }
+	}
+
+      if (bit1 > bit2)
+	return -1;
+      if (bit1 < bit2)
+	return 1;
+    }
+
+
+  /* Rule 10: Otherwise, leave the order unchanged.  */
+  return 0;
+}
+
+
 int
 getaddrinfo (const char *name, const char *service,
 	     const struct addrinfo *hints, struct addrinfo **pai)
 {
   int i = 0, j = 0, last_i = 0;
+  int nresults = 0;
   struct addrinfo *p = NULL, **end;
   struct gaih *g = gaih, *pg = NULL;
   struct gaih_service gaih_service, *pservice;
@@ -1000,7 +1332,11 @@ getaddrinfo (const char *name, const char *service,
 		  return -(i & GAIH_EAI);
 		}
 	      if (end)
-		while(*end) end = &((*end)->ai_next);
+		while (*end)
+		  {
+		    end = &((*end)->ai_next);
+		    ++nresults;
+		  }
 	    }
 	}
       ++g;
@@ -1009,6 +1345,46 @@ getaddrinfo (const char *name, const char *service,
   if (j == 0)
     return EAI_FAMILY;
 
+  if (nresults > 1)
+    {
+      /* Sort results according to RFC 3484.  */
+      struct sort_result results[nresults];
+      struct addrinfo *q;
+
+      for (i = 0, q = p; q != NULL; ++i, q = q->ai_next)
+	{
+	  results[i].dest_addr = q;
+	  results[i].got_source_addr = false;
+
+	  /* 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)
+	    {
+	      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);
+	    }
+	}
+
+      /* We got all the source addresses we can get, now sort using
+	 the information.  */
+      qsort (results, nresults, sizeof (results[0]), rfc3484_sort);
+
+      /* Queue the results up as they come out of sorting.  */
+      q = p = results[0].dest_addr;
+      for (i = 1; i < nresults; ++i)
+	q = q->ai_next = results[i].dest_addr;
+      q->ai_next = NULL;
+    }
+
   if (p)
     {
       *pai = p;