refactor getaddrinfo and add support for most remaining features

this is the first phase of the "resolver overhaul" project.

conceptually, the results of getaddrinfo are a direct product of a
list of address results and a list of service results. the new code
makes this explicit by computing these lists separately and combining
the results. this adds support for services that have both tcp and udp
versions, where the caller has not specified which it wants, and
eliminates a number of duplicate code paths which were all producing
the final output addrinfo structures, but in subtly different ways,
making it difficult to implement any of the features which were
missing.

in addition to the above benefits, the refactoring allows for legacy
functions like gethostbyname to be implemented without using the
getaddrinfo function itself. such changes to the legacy functions have
not yet been made, however.

further improvements include matching of service alias names from
/etc/services (previously only the primary name was supported),
returning multiple results from /etc/hosts (previously only the first
matching line was honored), and support for the AI_V4MAPPED and AI_ALL
flags.

features which remain unimplemented are IDN translations (encoding
non-ASCII hostnames for DNS lookup) and the AI_ADDRCONFIG flag.

at this point, the DNS-based name resolving code is still based on the
old interfaces in __dns.c, albeit somewhat simpler in its use of them.
there may be some dead code which could already be removed, but
changes to this layer will be a later phase of the resolver overhaul.
This commit is contained in:
Rich Felker 2014-05-31 20:57:54 -04:00
parent 5f4c496693
commit 6f409bff00
4 changed files with 359 additions and 227 deletions

View File

@ -1,249 +1,115 @@
#include <stdlib.h>
#include <stdio.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
#include <ctype.h>
#include "__dns.h"
#include "stdio_impl.h"
static int is_valid(const char *host)
{
const unsigned char *s;
if (strlen(host)-1 > 254 || mbstowcs(0, host, 0) > 255) return 0;
for (s=(void *)host; *s>=0x80 || *s=='.' || *s=='-' || isalnum(*s); s++);
return !*s;
}
#if 0
static int have_af(int family)
{
struct sockaddr_in6 sin6 = { .sin6_family = family };
socklen_t sl = family == AF_INET
? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6);
int sock = socket(family, SOCK_STREAM, 0);
int have = !bind(sock, (void *)&sin6, sl);
close(sock);
return have;
}
#endif
union sa {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
};
struct aibuf {
struct addrinfo ai;
union sa sa;
};
/* Extra slots needed for storing canonical name */
#define EXTRA ((256+sizeof(struct aibuf)-1)/sizeof(struct aibuf))
#include "lookup.h"
int getaddrinfo(const char *restrict host, const char *restrict serv, const struct addrinfo *restrict hint, struct addrinfo **restrict res)
{
int flags = hint ? hint->ai_flags : 0;
int family = hint ? hint->ai_family : AF_UNSPEC;
int type = hint ? hint->ai_socktype : 0;
int proto = hint ? hint->ai_protocol : 0;
unsigned long port = 0;
struct aibuf *buf;
union sa sa = {{0}};
unsigned char reply[1024];
int i, j;
char line[512];
FILE *f, _f;
unsigned char _buf[1024];
char *z;
int result;
int cnt;
struct service ports[MAXSERVS];
struct address addrs[MAXADDRS];
char canon[256], *outcanon;
int nservs, naddrs, nais, canon_len, i, j, k;
int family = AF_UNSPEC, flags = 0, proto = 0;
struct aibuf {
struct addrinfo ai;
union sa {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
} sa;
} *out;
if (family != AF_INET && family != AF_INET6 && family != AF_UNSPEC)
return EAI_FAMILY;
if (hint) {
family = hint->ai_family;
flags = hint->ai_flags;
proto = hint->ai_protocol;
if (host && strlen(host)>255) return EAI_NONAME;
if (serv && strlen(serv)>32) return EAI_SERVICE;
const int mask = AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST |
AI_V4MAPPED | AI_ALL | AI_ADDRCONFIG | AI_NUMERICSERV;
if ((flags & mask) != flags)
return EAI_BADFLAGS;
if (type && !proto)
proto = type==SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP;
if (!type && proto)
type = proto==IPPROTO_UDP ? SOCK_DGRAM : SOCK_STREAM;
switch (family) {
case AF_INET:
case AF_INET6:
case AF_UNSPEC:
break;
default:
return EAI_FAMILY;
}
if (serv) {
if (!*serv) return EAI_SERVICE;
port = strtoul(serv, &z, 10);
if (*z) {
size_t servlen = strlen(serv);
char *end = line;
if (flags & AI_NUMERICSERV) return EAI_SERVICE;
f = __fopen_rb_ca("/etc/services", &_f, _buf, sizeof _buf);
if (!f) return EAI_SERVICE;
while (fgets(line, sizeof line, f)) {
if (strncmp(line, serv, servlen) || !isspace(line[servlen]))
continue;
port = strtoul(line+servlen, &end, 10);
if (strncmp(end, proto==IPPROTO_UDP ? "/udp" : "/tcp", 4))
continue;
switch (hint->ai_socktype) {
case SOCK_STREAM:
switch (proto) {
case 0:
proto = IPPROTO_TCP;
case IPPROTO_TCP:
break;
default:
return EAI_SERVICE;
}
__fclose_ca(f);
if (feof(f)) return EAI_SERVICE;
}
if (port > 65535) return EAI_SERVICE;
port = htons(port);
}
if (!host) {
if (family == AF_UNSPEC) {
cnt = 2; family = AF_INET;
} else {
cnt = 1;
}
buf = calloc(sizeof *buf, cnt);
if (!buf) return EAI_MEMORY;
for (i=0; i<cnt; i++) {
if (i) family = AF_INET6;
buf[i].ai.ai_protocol = proto;
buf[i].ai.ai_socktype = type;
buf[i].ai.ai_addr = (void *)&buf[i].sa;
buf[i].ai.ai_addrlen = family==AF_INET6
? sizeof sa.sin6 : sizeof sa.sin;
buf[i].ai.ai_family = family;
buf[i].sa.sin.sin_family = family;
buf[i].sa.sin.sin_port = port;
if (i+1<cnt) buf[i].ai.ai_next = &buf[i+1].ai;
if (!(flags & AI_PASSIVE)) {
if (family == AF_INET) {
0[(uint8_t*)&buf[i].sa.sin.sin_addr.s_addr]=127;
3[(uint8_t*)&buf[i].sa.sin.sin_addr.s_addr]=1;
} else buf[i].sa.sin6.sin6_addr.s6_addr[15] = 1;
break;
case SOCK_DGRAM:
switch (proto) {
case 0:
proto = IPPROTO_UDP;
case IPPROTO_UDP:
break;
default:
return EAI_SERVICE;
}
case 0:
break;
default:
return EAI_SOCKTYPE;
}
*res = &buf->ai;
return 0;
}
if (!*host) return EAI_NONAME;
nservs = __lookup_serv(ports, serv, proto, flags);
if (nservs < 0) return nservs;
/* Try as a numeric address */
if (__ipparse(&sa, family, host) >= 0) {
buf = calloc(sizeof *buf, 1+EXTRA);
if (!buf) return EAI_MEMORY;
family = sa.sin.sin_family;
buf->ai.ai_protocol = proto;
buf->ai.ai_socktype = type;
buf->ai.ai_addr = (void *)&buf->sa;
buf->ai.ai_addrlen = family==AF_INET6 ? sizeof sa.sin6 : sizeof sa.sin;
buf->ai.ai_family = family;
buf->ai.ai_canonname = (char *)host;
buf->sa = sa;
buf->sa.sin.sin_port = port;
*res = &buf->ai;
return 0;
naddrs = __lookup_name(addrs, canon, host, family, flags);
if (naddrs < 0) return naddrs;
nais = nservs * naddrs;
canon_len = strlen(canon);
out = calloc(1, nais * sizeof(*out) + canon_len + 1);
if (!out) return EAI_MEMORY;
if (canon_len) {
outcanon = (void *)&out[nais];
memcpy(outcanon, canon, canon_len+1);
} else {
outcanon = 0;
}
if (flags & AI_NUMERICHOST) return EAI_NONAME;
f = __fopen_rb_ca("/etc/hosts", &_f, _buf, sizeof _buf);
if (f) while (fgets(line, sizeof line, f)) {
char *p;
size_t l = strlen(host);
if ((p=strchr(line, '#'))) *p++='\n', *p=0;
for(p=line+1; (p=strstr(p, host)) &&
(!isspace(p[-1]) || !isspace(p[l])); p++);
if (!p) continue;
__fclose_ca(f);
/* Isolate IP address to parse */
for (p=line; *p && !isspace(*p); p++);
*p++ = 0;
if (__ipparse(&sa, family, line) < 0) return EAI_NONAME;
/* Allocate and fill result buffer */
buf = calloc(sizeof *buf, 1+EXTRA);
if (!buf) return EAI_MEMORY;
family = sa.sin.sin_family;
buf->ai.ai_protocol = proto;
buf->ai.ai_socktype = type;
buf->ai.ai_addr = (void *)&buf->sa;
buf->ai.ai_addrlen = family==AF_INET6 ? sizeof sa.sin6 : sizeof sa.sin;
buf->ai.ai_family = family;
buf->sa = sa;
buf->sa.sin.sin_port = port;
/* Extract first name as canonical name */
for (; *p && isspace(*p); p++);
buf->ai.ai_canonname = (void *)(buf+1);
snprintf(buf->ai.ai_canonname, 256, "%s", p);
for (p=buf->ai.ai_canonname; *p && !isspace(*p); p++);
*p = 0;
if (!is_valid(buf->ai.ai_canonname))
buf->ai.ai_canonname = 0;
*res = &buf->ai;
return 0;
for (k=i=0; i<naddrs; i++) for (j=0; j<nservs; j++, k++) {
out[k].ai = (struct addrinfo){
.ai_family = addrs[i].family,
.ai_socktype = ports[j].proto == IPPROTO_TCP
? SOCK_STREAM : SOCK_DGRAM,
.ai_protocol = ports[j].proto,
.ai_addrlen = addrs[i].family == AF_INET
? sizeof(struct sockaddr_in)
: sizeof(struct sockaddr_in6),
.ai_addr = (void *)&out[k].sa,
.ai_canonname = outcanon,
.ai_next = &out[k+1].ai };
switch (addrs[i].family) {
case AF_INET:
out[k].sa.sin.sin_family = AF_INET;
out[k].sa.sin.sin_port = htons(ports[j].port);
memcpy(&out[k].sa.sin.sin_addr, &addrs[i].addr, 4);
break;
case AF_INET6:
out[k].sa.sin6.sin6_family = AF_INET6;
out[k].sa.sin6.sin6_port = htons(ports[j].port);
memcpy(&out[k].sa.sin6.sin6_addr, &addrs[i].addr, 16);
break;
}
}
if (f) __fclose_ca(f);
#if 0
f = __fopen_rb_ca("/etc/resolv.conf", &_f, _buf, sizeof _buf);
if (f) while (fgets(line, sizeof line, f)) {
if (!isspace(line[10]) || (strncmp(line, "search", 6)
&& strncmp(line, "domain", 6))) continue;
}
if (f) __fclose_ca(f);
#endif
/* Perform one or more DNS queries for host */
memset(reply, 0, sizeof reply);
result = __dns_query(reply, host, family, 0);
if (result < 0) return result;
cnt = __dns_count_addrs(reply, result);
if (cnt <= 0) return EAI_NONAME;
buf = calloc(sizeof *buf, cnt+EXTRA);
if (!buf) return EAI_MEMORY;
i = 0;
if (family != AF_INET6) {
j = __dns_get_rr(&buf[i].sa.sin.sin_addr, sizeof *buf, 4, cnt-i, reply, RR_A, 0);
while (j--) buf[i++].sa.sin.sin_family = AF_INET;
}
if (family != AF_INET) {
j = __dns_get_rr(&buf[i].sa.sin6.sin6_addr, sizeof *buf, 16, cnt-i, reply, RR_AAAA, 0);
while (j--) buf[i++].sa.sin.sin_family = AF_INET6;
}
if (result>1) {
j = __dns_get_rr(&buf[i].sa.sin.sin_addr, sizeof *buf, 4, cnt-i, reply+512, RR_A, 0);
while (j--) buf[i++].sa.sin.sin_family = AF_INET;
j = __dns_get_rr(&buf[i].sa.sin6.sin6_addr, sizeof *buf, 16, cnt-i, reply+512, RR_AAAA, 0);
while (j--) buf[i++].sa.sin.sin_family = AF_INET6;
}
if (__dns_get_rr((void *)&buf[cnt], 0, 256, 1, reply, RR_CNAME, 1) <= 0)
strcpy((void *)&buf[cnt], host);
for (i=0; i<cnt; i++) {
buf[i].ai.ai_protocol = proto;
buf[i].ai.ai_socktype = type;
buf[i].ai.ai_addr = (void *)&buf[i].sa;
buf[i].ai.ai_addrlen = buf[i].sa.sin.sin_family==AF_INET6
? sizeof sa.sin6 : sizeof sa.sin;
buf[i].ai.ai_family = buf[i].sa.sin.sin_family;
buf[i].sa.sin.sin_port = port;
buf[i].ai.ai_next = &buf[i+1].ai;
buf[i].ai.ai_canonname = (void *)&buf[cnt];
}
buf[cnt-1].ai.ai_next = 0;
*res = &buf->ai;
out[nais-1].ai.ai_next = 0;
*res = &out->ai;
return 0;
}

26
src/network/lookup.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef LOOKUP_H
#define LOOKUP_H
#include <stdint.h>
struct address {
int family;
unsigned scopeid;
uint8_t addr[16];
};
struct service {
uint16_t port;
char proto;
};
/* The limit of 48 results is a non-sharp bound on the number of addresses
* that can fit in one 512-byte DNS packet full of v4 results and a second
* packet full of v6 results. Due to headers, the actual limit is lower. */
#define MAXADDRS 48
#define MAXSERVS 2
int __lookup_serv(struct service buf[static MAXSERVS], const char *name, int proto, int flags);
int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family, int flags);
#endif

168
src/network/lookup_name.c Normal file
View File

@ -0,0 +1,168 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include "lookup.h"
#include "stdio_impl.h"
#include "syscall.h"
#include "__dns.h"
static int is_valid_hostname(const char *host)
{
const unsigned char *s;
if (strnlen(host, 255)-1 > 254 || mbstowcs(0, host, 0) > 255) return 0;
for (s=(void *)host; *s>=0x80 || *s=='.' || *s=='-' || isalnum(*s); s++);
return !*s;
}
static int name_from_null(struct address buf[static 2], const char *name, int family, int flags)
{
int cnt = 0;
if (name) return 0;
if (flags & AI_PASSIVE) {
if (family != AF_INET6)
buf[cnt++] = (struct address){ .family = AF_INET };
if (family != AF_INET)
buf[cnt++] = (struct address){ .family = AF_INET6 };
} else {
if (family != AF_INET6)
buf[cnt++] = (struct address){ .family = AF_INET, .addr = { 127,0,0,1 } };
if (family != AF_INET)
buf[cnt++] = (struct address){ .family = AF_INET6, .addr = { [15] = 1 } };
}
return cnt;
}
static int name_from_numeric(struct address buf[static 1], const char *name, int family)
{
struct in_addr a4;
struct in6_addr a6;
if (family != AF_INET6 && inet_aton(name, &a4)>0) {
memcpy(&buf[0].addr, &a4, sizeof a4);
buf[0].family = AF_INET;
return 1;
}
if (family != AF_INET && inet_pton(AF_INET6, name, &a6)>0) {
memcpy(&buf[0].addr, &a6, sizeof a6);
buf[0].family = AF_INET6;
return 1;
}
return 0;
}
static int name_from_hosts(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family)
{
char line[512];
size_t l = strlen(name);
int cnt = 0;
unsigned char _buf[1032];
FILE _f, *f = __fopen_rb_ca("/etc/hosts", &_f, _buf, sizeof _buf);
if (!f) return 0;
while (fgets(line, sizeof line, f) && cnt < MAXADDRS) {
char *p, *z;
if ((p=strchr(line, '#'))) *p++='\n', *p=0;
for(p=line+1; (p=strstr(p, name)) &&
(!isspace(p[-1]) || !isspace(p[l])); p++);
if (!p) continue;
/* Isolate IP address to parse */
for (p=line; *p && !isspace(*p); p++);
*p++ = 0;
if (name_from_numeric(buf+cnt, line, family))
cnt++;
/* Extract first name as canonical name */
for (; *p && isspace(*p); p++);
for (z=p; *z && !isspace(*z); z++);
*z = 0;
if (is_valid_hostname(p)) memcpy(canon, p, z-p+1);
}
__fclose_ca(f);
return cnt;
}
static int name_from_dns(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family)
{
unsigned char reply[1024] = { 0 }, *p = reply;
char tmp[256];
int i, cnt = 0;
/* Perform one or more DNS queries for host */
int result = __dns_query(reply, name, family, 0);
if (result < 0) return result;
for (i=0; i<result; i++) {
if (family != AF_INET6) {
int j = __dns_get_rr(&buf[cnt].addr, sizeof *buf, 4, MAXADDRS-cnt, p, RR_A, 0);
while (j--) buf[cnt++].family = AF_INET;
}
if (family != AF_INET) {
int j = __dns_get_rr(&buf[cnt].addr, sizeof *buf, 16, MAXADDRS-cnt, p, RR_AAAA, 0);
while (j--) buf[cnt++].family = AF_INET6;
}
p += 512;
}
__dns_get_rr(tmp, 0, 256, 1, reply, RR_CNAME, 1);
if (is_valid_hostname(tmp)) strcpy(canon, tmp);
return cnt;
}
int __lookup_name(struct address buf[static MAXADDRS], char canon[static 256], const char *name, int family, int flags)
{
int cnt = 0, i, j;
*canon = 0;
if (name) {
size_t l;
if ((l = strnlen(name, 255))-1 > 254)
return EAI_NONAME;
memcpy(canon, name, l+1);
}
/* Procedurally, a request for v6 addresses with the v4-mapped
* flag set is like a request for unspecified family, followed
* by filtering of the results. */
if (flags & AI_V4MAPPED) {
if (family == AF_INET6) family = AF_UNSPEC;
else flags -= AI_V4MAPPED;
}
/* Try each backend until there's at least one result. */
cnt = name_from_null(buf, name, family, flags);
if (cnt<=0) cnt = name_from_numeric(buf, name, family);
if (cnt<=0 && !(flags & AI_NUMERICHOST)) {
cnt = name_from_hosts(buf, canon, name, family);
if (cnt<=0) cnt = name_from_dns(buf, canon, name, family);
}
if (cnt<=0) return cnt ? cnt : EAI_NONAME;
/* Filter/transform results for v4-mapped lookup, if requested. */
if (flags & AI_V4MAPPED) {
if (!(flags & AI_ALL)) {
/* If any v6 results exist, remove v4 results. */
for (i=0; i<cnt && buf[i].family != AF_INET6; i++);
if (i<cnt) {
for (j=0; i<cnt; i++) {
if (buf[i].family == AF_INET6)
buf[j++] = buf[i];
}
cnt = i = j;
}
}
/* Translate any remaining v4 results to v6 */
for (i=0; i<cnt; i++) {
if (buf[i].family != AF_INET) continue;
memcpy(buf[i].addr+12, buf[i].addr, 4);
memcpy(buf[i].addr, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12);
buf[i].scopeid = 0;
buf[i].family = AF_INET6;
}
}
return cnt;
}

72
src/network/lookup_serv.c Normal file
View File

@ -0,0 +1,72 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include "lookup.h"
#include "stdio_impl.h"
int __lookup_serv(struct service buf[static MAXSERVS], const char *name, int proto, int flags)
{
char line[128];
int cnt = 0;
char *p, *z = "";
unsigned long port = 0;
if (name) {
if (!*name) return EAI_SERVICE;
port = strtoul(name, &z, 10);
}
if (!*z) {
if (port > 65535) return EAI_SERVICE;
if (proto != IPPROTO_UDP) {
buf[cnt].port = port;
buf[cnt++].proto = IPPROTO_TCP;
}
if (proto != IPPROTO_TCP) {
buf[cnt].port = port;
buf[cnt++].proto = IPPROTO_UDP;
}
return cnt;
}
if (flags & AI_NUMERICSERV) return EAI_SERVICE;
size_t l = strlen(name);
unsigned char _buf[1032];
FILE _f, *f = __fopen_rb_ca("/etc/services", &_f, _buf, sizeof _buf);
if (!f) return EAI_SERVICE;
while (fgets(line, sizeof line, f) && cnt < MAXSERVS) {
if ((p=strchr(line, '#'))) *p++='\n', *p=0;
/* Find service name */
for(p=line; (p=strstr(p, name)); p++) {
if (p>line && !isspace(p[-1])) continue;
if (p[l] && !isspace(p[l])) continue;
break;
}
if (!p) continue;
/* Skip past canonical name at beginning of line */
for (p=line; *p && !isspace(*p); p++);
if (!p) continue;
port = strtoul(p, &z, 10);
if (port > 65535 || z==p) continue;
if (!strncmp(z, "/udp", 4)) {
if (proto == IPPROTO_TCP) continue;
buf[cnt].port = port;
buf[cnt++].proto = IPPROTO_UDP;
}
if (!strncmp(z, "/tcp", 4)) {
if (proto == IPPROTO_UDP) continue;
buf[cnt].port = port;
buf[cnt++].proto = IPPROTO_TCP;
}
}
__fclose_ca(f);
return cnt > 0 ? cnt : EAI_SERVICE;
}