implement result address sorting in the resolver (getaddrinfo, etc.)
authorRich Felker <dalias@aerifal.cx>
Sat, 21 Jun 2014 23:21:05 +0000 (19:21 -0400)
committerRich Felker <dalias@aerifal.cx>
Sat, 21 Jun 2014 23:21:05 +0000 (19:21 -0400)
src/network/lookup.h
src/network/lookup_name.c

index 19c9e48..4e45d86 100644 (file)
@@ -7,6 +7,7 @@ struct address {
        int family;
        unsigned scopeid;
        uint8_t addr[16];
+       int sortkey;
 };
 
 struct service {
index 743aa08..0225a93 100644 (file)
@@ -7,6 +7,8 @@
 #include <stdlib.h>
 #include <string.h>
 #include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
 #include "lookup.h"
 #include "stdio_impl.h"
 #include "syscall.h"
@@ -146,6 +148,80 @@ static int name_from_dns(struct address buf[static MAXADDRS], char canon[static
        return EAI_FAIL;
 }
 
+static const struct policy {
+       unsigned char addr[16];
+       unsigned char len, mask;
+       unsigned char prec, label;
+} defpolicy[] = {
+       { "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1", 15, 0xff, 50, 0 },
+       { "\0\0\0\0\0\0\0\0\0\0\xff\xff", 11, 0xff, 35, 4 },
+       { "\x20\2", 1, 0xff, 30, 2 },
+       { "\x20\1", 3, 0xff, 5, 5 },
+       { "\xfc", 0, 0xfe, 3, 13 },
+#if 0
+       /* These are deprecated and/or returned to the address
+        * pool, so despite the RFC, treating them as special
+        * is probably wrong. */
+       { "", 11, 0xff, 1, 3 },
+       { "\xfe\xc0", 1, 0xc0, 1, 11 },
+       { "\x3f\xfe", 1, 0xff, 1, 12 },
+#endif
+       /* Last rule must match all addresses to stop loop. */
+       { "", 0, 0, 40, 1 },
+};
+
+static const struct policy *policyof(const struct in6_addr *a)
+{
+       int i;
+       for (i=0; ; i++) {
+               if (memcmp(a->s6_addr, defpolicy[i].addr, defpolicy[i].len))
+                       continue;
+               if ((a->s6_addr[defpolicy[i].len] & defpolicy[i].mask)
+                   != defpolicy[i].addr[defpolicy[i].len])
+                       continue;
+               return defpolicy+i;
+       }
+}
+
+static int labelof(const struct in6_addr *a)
+{
+       return policyof(a)->label;
+}
+
+static int scopeof(const struct in6_addr *a)
+{
+       if (IN6_IS_ADDR_MULTICAST(a)) return a->s6_addr[1] & 15;
+       if (IN6_IS_ADDR_LINKLOCAL(a)) return 2;
+       if (IN6_IS_ADDR_LOOPBACK(a)) return 2;
+       if (IN6_IS_ADDR_SITELOCAL(a)) return 5;
+       return 14;
+}
+
+static int prefixmatch(const struct in6_addr *s, const struct in6_addr *d)
+{
+       /* FIXME: The common prefix length should be limited to no greater
+        * than the nominal length of the prefix portion of the source
+        * address. However the definition of the source prefix length is
+        * not clear and thus this limiting is not yet implemented. */
+       unsigned i;
+       for (i=0; i<128 && !((s->s6_addr[i/8]^d->s6_addr[i/8])&(128>>(i%8))); i++);
+       return i;
+}
+
+#define DAS_USABLE              0x40000000
+#define DAS_MATCHINGSCOPE       0x20000000
+#define DAS_MATCHINGLABEL       0x10000000
+#define DAS_PREC_SHIFT          20
+#define DAS_SCOPE_SHIFT         16
+#define DAS_PREFIX_SHIFT        8
+#define DAS_ORDER_SHIFT         0
+
+static int addrcmp(const void *_a, const void *_b)
+{
+       const struct address *a = _a, *b = _b;
+       return b->sortkey - a->sortkey;
+}
+
 int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family, int flags)
 {
        int cnt = 0, i, j;
@@ -198,5 +274,64 @@ int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], c
                }
        }
 
+       /* No further processing is needed if there are fewer than 2
+        * results or if there are only IPv4 results. */
+       if (cnt<2 || family==AF_INET) return cnt;
+       for (i=0; buf[i].family == AF_INET; i++)
+               if (i==cnt) return cnt;
+
+       int cs;
+       pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cs);
+
+       /* The following implements a subset of RFC 3484/6724 destination
+        * address selection by generating a single 31-bit sort key for
+        * each address. Rules 3, 4, and 7 are omitted for having
+        * excessive runtime and code size cost and dubious benefit.
+        * So far the label/precedence table cannot be customized. */
+       for (i=0; i<cnt; i++) {
+               int key = 0;
+               struct sockaddr_in6 sa, da = {
+                       .sin6_family = AF_INET6,
+                       .sin6_scope_id = buf[i].scopeid,
+                       .sin6_port = 65535
+               };
+               if (buf[i].family == AF_INET6) {
+                       memcpy(da.sin6_addr.s6_addr, buf[i].addr, 16);
+               } else {
+                       memcpy(da.sin6_addr.s6_addr,
+                               "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
+                       memcpy(da.sin6_addr.s6_addr+12, buf[i].addr, 4);
+               }
+               const struct policy *dpolicy = policyof(&da.sin6_addr);
+               int dscope = scopeof(&da.sin6_addr);
+               int dlabel = dpolicy->label;
+               int dprec = dpolicy->prec;
+               int prefixlen = 0;
+               int fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP);
+               if (fd >= 0) {
+                       if (!connect(fd, (void *)&da, sizeof da)) {
+                               key |= DAS_USABLE;
+                               if (!getsockname(fd, (void *)&sa,
+                                   &(socklen_t){sizeof sa})) {
+                                       if (dscope == scopeof(&sa.sin6_addr))
+                                               key |= DAS_MATCHINGSCOPE;
+                                       if (dlabel == labelof(&sa.sin6_addr))
+                                               key |= DAS_MATCHINGLABEL;
+                                       prefixlen = prefixmatch(&sa.sin6_addr,
+                                               &da.sin6_addr);
+                               }
+                       }
+                       close(fd);
+               }
+               key |= dprec << DAS_PREC_SHIFT;
+               key |= (15-dscope) << DAS_SCOPE_SHIFT;
+               key |= prefixlen << DAS_PREFIX_SHIFT;
+               key |= (MAXADDRS-i) << DAS_ORDER_SHIFT;
+               buf[i].sortkey = key;
+       }
+       qsort(buf, cnt, sizeof *buf, addrcmp);
+
+       pthread_setcancelstate(cs, 0);
+
        return cnt;
 }