Module Name: src Committed By: christos Date: Fri May 3 19:31:13 UTC 2013
Modified Files: src/lib/libc/net: getaddrinfo.c Log Message: PR/32373, PR/25827: Add SRV lookup in getaddrinfo(3) Per DNS-SD (RFC 2782), but only enabled if AI_SRV is set. To generate a diff of this commit: cvs rdiff -u -r1.102 -r1.103 src/lib/libc/net/getaddrinfo.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/lib/libc/net/getaddrinfo.c diff -u src/lib/libc/net/getaddrinfo.c:1.102 src/lib/libc/net/getaddrinfo.c:1.103 --- src/lib/libc/net/getaddrinfo.c:1.102 Fri May 3 15:24:52 2013 +++ src/lib/libc/net/getaddrinfo.c Fri May 3 15:31:13 2013 @@ -1,4 +1,4 @@ -/* $NetBSD: getaddrinfo.c,v 1.102 2013/05/03 19:24:52 christos Exp $ */ +/* $NetBSD: getaddrinfo.c,v 1.103 2013/05/03 19:31:13 christos Exp $ */ /* $KAME: getaddrinfo.c,v 1.29 2000/08/31 17:26:57 itojun Exp $ */ /* @@ -55,7 +55,7 @@ #include <sys/cdefs.h> #if defined(LIBC_SCCS) && !defined(lint) -__RCSID("$NetBSD: getaddrinfo.c,v 1.102 2013/05/03 19:24:52 christos Exp $"); +__RCSID("$NetBSD: getaddrinfo.c,v 1.103 2013/05/03 19:31:13 christos Exp $"); #endif /* LIBC_SCCS and not lint */ #include "namespace.h" @@ -191,6 +191,13 @@ struct res_target { int n; /* result length */ }; +struct srvinfo { + struct srvinfo *next; + char name[MAXDNAME]; + int port, pri, weight; +}; + +static int gai_srvok(const char *); static int str2number(const char *); static int explore_fqdn(const struct addrinfo *, const char *, const char *, struct addrinfo **, struct servent_data *); @@ -217,6 +224,12 @@ static int ip6_str2scopeid(char *, struc static struct addrinfo *getanswer(const querybuf *, int, const char *, int, const struct addrinfo *); static void aisort(struct addrinfo *s, res_state res); +static struct addrinfo * _dns_query(struct res_target *, + const struct addrinfo *, res_state, int); +static struct addrinfo * _dns_srv_lookup(const char *, const char *, + const struct addrinfo *); +static struct addrinfo * _dns_host_lookup(const char *, + const struct addrinfo *); static int _dns_getaddrinfo(void *, void *, va_list); static void _sethtent(FILE **); static void _endhtent(FILE **); @@ -319,6 +332,58 @@ freeaddrinfo(struct addrinfo *ai) } while (ai); } +/* + * We don't want localization to affect us + */ +#define PERIOD '.' +#define hyphenchar(c) ((c) == '-') +#define periodchar(c) ((c) == PERIOD) +#define underschar(c) ((c) == '_') +#define alphachar(c) (((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z')) +#define digitchar(c) ((c) >= '0' && (c) <= '9') + +#define firstchar(c) (alphachar(c) || digitchar(c) || underschar(c)) +#define lastchar(c) (alphachar(c) || digitchar(c)) +#define middlechar(c) (lastchar(c) || hyphenchar(c)) + +static int +gai_srvok(const char *dn) +{ + int nch, pch, ch; + + for (pch = PERIOD, nch = ch = *dn++; ch != '\0'; pch = ch, ch = nch) { + if (periodchar(ch)) + continue; + if (periodchar(pch)) { + if (!firstchar(ch)) + return 0; + } else if (periodchar(nch) || nch == '\0') { + if (!lastchar(ch)) + return 0; + } else if (!middlechar(ch)) + return 0; + } + return 1; +} + +static in_port_t * +getport(struct addrinfo *ai) { + static in_port_t p; + + switch (ai->ai_family) { + case AF_INET: + return &((struct sockaddr_in *)(void *)ai->ai_addr)->sin_port; +#ifdef INET6 + case AF_INET6: + return &((struct sockaddr_in6 *)(void *)ai->ai_addr)->sin6_port; +#endif + default: + p = 0; + /* XXX: abort()? */ + return &p; + } +} + static int str2number(const char *p) { @@ -589,7 +654,7 @@ explore_fqdn(const struct addrinfo *pai, return 0; switch (nsdispatch(&result, dtab, NSDB_HOSTS, "getaddrinfo", - default_dns_files, hostname, pai)) { + default_dns_files, hostname, pai, servname)) { case NS_TRYAGAIN: error = EAI_AGAIN; goto free; @@ -602,6 +667,9 @@ explore_fqdn(const struct addrinfo *pai, case NS_SUCCESS: error = 0; for (cur = result; cur; cur = cur->ai_next) { + /* Check for already filled port. */ + if (*getport(cur)) + continue; GET_PORT(cur, servname, svd); /* canonname should be filled already */ } @@ -990,21 +1058,8 @@ get_port(const struct addrinfo *ai, cons port = sp->s_port; } - if (!matchonly) { - switch (ai->ai_family) { - case AF_INET: - ((struct sockaddr_in *)(void *) - ai->ai_addr)->sin_port = port; - break; -#ifdef INET6 - case AF_INET6: - ((struct sockaddr_in6 *)(void *) - ai->ai_addr)->sin6_port = port; - break; -#endif - } - } - + if (!matchonly) + *getport(__UNCONST(ai)) = port; return 0; } @@ -1107,7 +1162,7 @@ getanswer(const querybuf *answer, int an const struct addrinfo *pai) { struct addrinfo sentinel, *cur; - struct addrinfo ai; + struct addrinfo ai, *aip; const struct afd *afd; char *canonname; const HEADER *hp; @@ -1120,6 +1175,8 @@ getanswer(const querybuf *answer, int an char tbuf[MAXDNAME]; int (*name_ok) (const char *); char hostbuf[8*1024]; + int port, pri, weight; + struct srvinfo *srvlist, *srv, *csrv; _DIAGASSERT(answer != NULL); _DIAGASSERT(qname != NULL); @@ -1136,6 +1193,9 @@ getanswer(const querybuf *answer, int an case T_ANY: /*use T_ANY only for T_A/T_AAAA lookup*/ name_ok = res_hnok; break; + case T_SRV: + name_ok = gai_srvok; + break; default: return NULL; /* XXX should be abort(); */ } @@ -1175,6 +1235,7 @@ getanswer(const querybuf *answer, int an } haveanswer = 0; had_error = 0; + srvlist = NULL; while (ancount-- > 0 && cp < eom && !had_error) { n = dn_expand(answer->buf, eom, cp, bp, (int)(ep - bp)); if ((n < 0) || !(*name_ok)(bp)) { @@ -1277,17 +1338,116 @@ getanswer(const querybuf *answer, int an cur = cur->ai_next; cp += n; break; + case T_SRV: + /* Add to SRV list. Insertion sort on priority. */ + pri = _getshort(cp); + cp += INT16SZ; + weight = _getshort(cp); + cp += INT16SZ; + port = _getshort(cp); + cp += INT16SZ; + n = dn_expand(answer->buf, eom, cp, tbuf, + (int)sizeof(tbuf)); + if ((n < 0) || !res_hnok(tbuf)) { + had_error++; + continue; + } + cp += n; + if (strlen(tbuf) + 1 >= MAXDNAME) { + had_error++; + continue; + } + srv = malloc(sizeof(*srv)); + if (!srv) { + had_error++; + continue; + } + strlcpy(srv->name, tbuf, sizeof(srv->name)); + srv->pri = pri; + srv->weight = weight; + srv->port = port; + /* Weight 0 is sorted before other weights. */ + if (!srvlist + || srv->pri < srvlist->pri + || (srv->pri == srvlist->pri && + (!srv->weight || srvlist->weight))) { + srv->next = srvlist; + srvlist = srv; + } else { + for (csrv = srvlist; + csrv->next && csrv->next->pri <= srv->pri; + csrv = csrv->next) { + if (csrv->next->pri == srv->pri + && (!srv->weight || + csrv->next->weight)) + break; + } + srv->next = csrv->next; + csrv->next = srv; + } + continue; /* Don't add to haveanswer yet. */ default: abort(); } if (!had_error) haveanswer++; } + + if (srvlist) { + res_state res; + /* + * Check for explicit rejection. + */ + if (!srvlist->next && !srvlist->name[0]) { + free(srvlist); + h_errno = HOST_NOT_FOUND; + return NULL; + } + res = __res_get_state(); + if (res == NULL) { + h_errno = NETDB_INTERNAL; + return NULL; + } + + while (srvlist) { + struct res_target q, q2; + + srv = srvlist; + srvlist = srvlist->next; + + /* + * Since res_* doesn't give the additional + * section, we always look up. + */ + memset(&q, 0, sizeof(q)); + memset(&q2, 0, sizeof(q2)); + + q.name = srv->name; + q.qclass = C_IN; + q.qtype = T_AAAA; + q.next = &q2; + q2.name = srv->name; + q2.qclass = C_IN; + q2.qtype = T_A; + + aip = _dns_query(&q, pai, res, 0); + + if (aip != NULL) { + cur->ai_next = aip; + while (cur && cur->ai_next) { + cur = cur->ai_next; + *getport(cur) = htons(srv->port); + haveanswer++; + } + } + free(srv); + } + __res_put_state(res); + } if (haveanswer) { - if (!canonname) - (void)get_canonname(pai, sentinel.ai_next, qname); - else - (void)get_canonname(pai, sentinel.ai_next, canonname); + if (!sentinel.ai_next->ai_canonname) + (void)get_canonname(pai, sentinel.ai_next, + canonname ? canonname : qname); h_errno = NETDB_SUCCESS; return sentinel.ai_next; } @@ -1327,117 +1487,251 @@ aisort(struct addrinfo *s, res_state res s->ai_next = head.ai_next; } +static struct addrinfo * +_dns_query(struct res_target *q, const struct addrinfo *pai, + res_state res, int dosearch) +{ + struct res_target *q2 = q->next; + querybuf *buf, *buf2; + struct addrinfo sentinel, *cur, *ai; + +#ifdef DNS_DEBUG + struct res_target *iter; + for (iter = q; iter; iter = iter->next) + printf("Query type %d for %s\n", iter->qtype, iter->name); +#endif + + buf = malloc(sizeof(*buf)); + if (buf == NULL) { + h_errno = NETDB_INTERNAL; + return NULL; + } + buf2 = malloc(sizeof(*buf2)); + if (buf2 == NULL) { + free(buf); + h_errno = NETDB_INTERNAL; + return NULL; + } + + memset(&sentinel, 0, sizeof(sentinel)); + cur = &sentinel; + + q->answer = buf->buf; + q->anslen = sizeof(buf->buf); + if (q2) { + q2->answer = buf2->buf; + q2->anslen = sizeof(buf2->buf); + } + + if (dosearch) { + if (res_searchN(q->name, q, res) < 0) + goto out; + } else { + if (res_queryN(q->name, q, res) < 0) + goto out; + } + + ai = getanswer(buf, q->n, q->name, q->qtype, pai); + if (ai) { + cur->ai_next = ai; + while (cur && cur->ai_next) + cur = cur->ai_next; + } + if (q2) { + ai = getanswer(buf2, q2->n, q2->name, q2->qtype, pai); + if (ai) + cur->ai_next = ai; + } + free(buf); + free(buf2); + return sentinel.ai_next; +out: + free(buf); + free(buf2); + return NULL; +} + /*ARGSUSED*/ -static int -_dns_getaddrinfo(void *rv, void *cb_data, va_list ap) +static struct addrinfo * +_dns_srv_lookup(const char *name, const char *servname, + const struct addrinfo *pai) { - struct addrinfo *ai; - querybuf *buf, *buf2; - const char *name; - const struct addrinfo *pai; - struct addrinfo sentinel, *cur; - struct res_target q, q2; + static const char * const srvprotos[] = { "tcp", "udp" }; + static const int srvnottype[] = { SOCK_DGRAM, SOCK_STREAM }; + static const int nsrvprotos = 2; + struct addrinfo sentinel, *cur, *ai; + struct servent *serv, sv; + struct servent_data svd; + struct res_target q; res_state res; + char *tname; + int i; - name = va_arg(ap, char *); - pai = va_arg(ap, const struct addrinfo *); + res = __res_get_state(); + if (res == NULL) + return NULL; - memset(&q, 0, sizeof(q)); - memset(&q2, 0, sizeof(q2)); + memset(&svd, 0, sizeof(svd)); memset(&sentinel, 0, sizeof(sentinel)); cur = &sentinel; - buf = malloc(sizeof(*buf)); - if (buf == NULL) { - h_errno = NETDB_INTERNAL; - return NS_NOTFOUND; - } - buf2 = malloc(sizeof(*buf2)); - if (buf2 == NULL) { - free(buf); - h_errno = NETDB_INTERNAL; - return NS_NOTFOUND; + /* + * Iterate over supported SRV protocols. + * (currently UDP and TCP only) + */ + for (i = 0; i < nsrvprotos; i++) { + /* + * Check that the caller didn't specify a hint + * which precludes this protocol. + */ + if (pai->ai_socktype == srvnottype[i]) + continue; + /* + * If the caller specified a port, + * then lookup the database for the + * official service name. + */ + serv = getservbyname_r(servname, srvprotos[i], &sv, &svd); + if (serv == NULL) + continue; + + /* + * Construct service DNS name. + */ + if (asprintf(&tname, "_%s._%s.%s", serv->s_name, serv->s_proto, + name) < 0) + continue; + + memset(&q, 0, sizeof(q)); + q.name = tname; + q.qclass = C_IN; + q.qtype = T_SRV; + + /* + * Do SRV query. + */ + ai = _dns_query(&q, pai, res, 1); + if (ai) { + cur->ai_next = ai; + while (cur && cur->ai_next) + cur = cur->ai_next; + } + free(tname); } + if (res->nsort) + aisort(&sentinel, res); + + __res_put_state(res); + + return sentinel.ai_next; +} + +/*ARGSUSED*/ +static struct addrinfo * +_dns_host_lookup(const char *name, const struct addrinfo *pai) +{ + struct res_target q, q2; + struct addrinfo sentinel, *ai; + res_state res; + + res = __res_get_state(); + if (res == NULL) + return NULL; + + memset(&q, 0, sizeof(q2)); + memset(&q2, 0, sizeof(q2)); + switch (pai->ai_family) { case AF_UNSPEC: /* prefer IPv6 */ q.name = name; q.qclass = C_IN; q.qtype = T_AAAA; - q.answer = buf->buf; - q.anslen = sizeof(buf->buf); q.next = &q2; q2.name = name; q2.qclass = C_IN; q2.qtype = T_A; - q2.answer = buf2->buf; - q2.anslen = sizeof(buf2->buf); break; case AF_INET: q.name = name; q.qclass = C_IN; q.qtype = T_A; - q.answer = buf->buf; - q.anslen = sizeof(buf->buf); break; case AF_INET6: q.name = name; q.qclass = C_IN; q.qtype = T_AAAA; - q.answer = buf->buf; - q.anslen = sizeof(buf->buf); break; default: - free(buf); - free(buf2); - return NS_UNAVAIL; + h_errno = NETDB_INTERNAL; + return NULL; } - res = __res_get_state(); - if (res == NULL) { - free(buf); - free(buf2); - return NS_NOTFOUND; - } + ai = _dns_query(&q, pai, res, 1); - if (res_searchN(name, &q, res) < 0) { - __res_put_state(res); - free(buf); - free(buf2); - return NS_NOTFOUND; - } - ai = getanswer(buf, q.n, q.name, q.qtype, pai); - if (ai) { - cur->ai_next = ai; - while (cur && cur->ai_next) - cur = cur->ai_next; - } - if (q.next) { - ai = getanswer(buf2, q2.n, q2.name, q2.qtype, pai); - if (ai) - cur->ai_next = ai; - } - free(buf); - free(buf2); - if (sentinel.ai_next == NULL) { - __res_put_state(res); - switch (h_errno) { - case HOST_NOT_FOUND: - return NS_NOTFOUND; - case TRY_AGAIN: - return NS_TRYAGAIN; - default: - return NS_UNAVAIL; - } - } + memset(&sentinel, 0, sizeof(sentinel)); + sentinel.ai_next = ai; - if (res->nsort) + if (ai != NULL && res->nsort) aisort(&sentinel, res); __res_put_state(res); - *((struct addrinfo **)rv) = sentinel.ai_next; + return sentinel.ai_next; +} + +/*ARGSUSED*/ +static int +_dns_getaddrinfo(void *rv, void *cb_data, va_list ap) +{ + struct addrinfo *ai = NULL; + const char *name, *servname; + const struct addrinfo *pai; + + name = va_arg(ap, char *); + pai = va_arg(ap, const struct addrinfo *); + servname = va_arg(ap, char *); + + /* + * Try doing SRV lookup on service first. + */ + if (servname +#ifdef AI_SRV + && (pai->ai_flags & AI_SRV) +#endif + && !(pai->ai_flags & AI_NUMERICSERV) + && str2number(servname) == -1) { + +#ifdef DNS_DEBUG + printf("%s: try SRV lookup\n", __func__); +#endif + ai = _dns_srv_lookup(name, servname, pai); + } + + /* + * Do lookup on name. + */ + if (ai == NULL) { + +#ifdef DNS_DEBUG + printf("%s: try HOST lookup\n", __func__); +#endif + ai = _dns_host_lookup(name, pai); + + if (ai == NULL) { + switch (h_errno) { + case HOST_NOT_FOUND: + return NS_NOTFOUND; + case TRY_AGAIN: + return NS_TRYAGAIN; + default: + return NS_UNAVAIL; + } + } + } + + *((struct addrinfo **)rv) = ai; return NS_SUCCESS; } @@ -1996,3 +2290,33 @@ res_querydomainN(const char *name, const } return res_queryN(longname, target, res); } + +#ifdef TEST +int +main(int argc, char *argv[]) { + struct addrinfo *ai, *sai; + int i, e; + char buf[1024]; + + for (i = 1; i < argc; i++) { + if ((e = getaddrinfo(argv[i], NULL, NULL, &sai)) != 0) + warnx("%s: %s", argv[i], gai_strerror(e)); + for (ai = sai; ai; ai = ai->ai_next) { + sockaddr_snprintf(buf, sizeof(buf), "%a", ai->ai_addr); + printf("flags=0x%x family=%d socktype=%d protocol=%d " + "addrlen=%zu addr=%s canonname=%s next=%p\n", + ai->ai_flags, + ai->ai_family, + ai->ai_socktype, + ai->ai_protocol, + (size_t)ai->ai_addrlen, + buf, + ai->ai_canonname, + ai->ai_next); + } + if (sai) + freeaddrinfo(sai); + } + return 0; +} +#endif