diff options
| -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;  } | 
