diff options
author | Ulrich Drepper <drepper@redhat.com> | 2003-11-18 07:04:13 +0000 |
---|---|---|
committer | Ulrich Drepper <drepper@redhat.com> | 2003-11-18 07:04:13 +0000 |
commit | 5ddb5bf5fbceb6f44f04f7267eb214eee5fc2f90 (patch) | |
tree | bc7e7d7c1fd3d3c434853a2971562d661a6e3e58 /sysdeps | |
parent | 9780c971fbf99df9bfc92116e0779a40070668b0 (diff) | |
download | glibc-5ddb5bf5fbceb6f44f04f7267eb214eee5fc2f90.tar.gz glibc-5ddb5bf5fbceb6f44f04f7267eb214eee5fc2f90.tar.xz glibc-5ddb5bf5fbceb6f44f04f7267eb214eee5fc2f90.zip |
Update.
2003-11-17 Ulrich Drepper <drepper@redhat.com> * sysdeps/posix/getaddrinfo.c: Add support for destination address selection according to RFC 3484.
Diffstat (limited to 'sysdeps')
-rw-r--r-- | sysdeps/posix/getaddrinfo.c | 378 |
1 files changed, 377 insertions, 1 deletions
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; |