summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRich Felker <dalias@aerifal.cx>2014-06-21 19:21:05 -0400
committerRich Felker <dalias@aerifal.cx>2014-06-21 19:21:05 -0400
commit3c42605a603485df60ed42792c3d3113a4c47a90 (patch)
tree5d30817e67f0996466abb23837734e354d8ec4d6
parent70d9c303b3115ab0fe6060ba0f7b0e4c0a2320b7 (diff)
downloadmusl-3c42605a603485df60ed42792c3d3113a4c47a90.tar.gz
implement result address sorting in the resolver (getaddrinfo, etc.)
-rw-r--r--src/network/lookup.h1
-rw-r--r--src/network/lookup_name.c135
2 files changed, 136 insertions, 0 deletions
diff --git a/src/network/lookup.h b/src/network/lookup.h
index 19c9e488..4e45d869 100644
--- a/src/network/lookup.h
+++ b/src/network/lookup.h
@@ -7,6 +7,7 @@ struct address {
int family;
unsigned scopeid;
uint8_t addr[16];
+ int sortkey;
};
struct service {
diff --git a/src/network/lookup_name.c b/src/network/lookup_name.c
index 743aa082..0225a934 100644
--- a/src/network/lookup_name.c
+++ b/src/network/lookup_name.c
@@ -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;
}