diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/network/lookup.h | 1 | ||||
-rw-r--r-- | src/network/lookup_name.c | 135 |
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; } |