Subject: lib/25827: DNS service discovery in getaddrinfo()
To: None <gnats-bugs@gnats.NetBSD.org>
From: None <morth@morth.org>
List: netbsd-bugs
Date: 06/05/2004 16:05:52
>Number: 25827
>Category: lib
>Synopsis: DNS service discovery in getaddrinfo()
>Confidential: no
>Severity: non-critical
>Priority: low
>Responsible: lib-bug-people
>State: open
>Class: change-request
>Submitter-Id: net
>Arrival-Date: Sat Jun 05 14:07:00 UTC 2004
>Closed-Date:
>Last-Modified:
>Originator:
>Release: NetBSD 2.0F
>Organization:
>Environment:
System: NetBSD moroten.morth.org 2.0F NetBSD 2.0F (morth) #1: Fri Jun 4 13:08:55 CEST 2004 morth@moroten.morth.org:/mnt/kaninen/users/morth/Unix/netbsd/obj/sys/arch/macppc/compile/morth macppc
Architecture: powerpc
Machine: macppc
>Description:
Included is a patch for adding DNS-SD (RFC 2782) to getaddrinfo().
It has been discussed on tech-net in a thread starting here:
http://mail-index.netbsd.org/tech-net/2004/06/01/0000.html
>How-To-Repeat:
N/A
>Fix:
Index: include/netdb.h
===================================================================
RCS file: /cvsroot/src/include/netdb.h,v
retrieving revision 1.42
diff -u -r1.42 netdb.h
--- include/netdb.h 25 May 2004 14:49:38 -0000 1.42
+++ include/netdb.h 4 Jun 2004 09:11:29 -0000
@@ -253,9 +253,17 @@
#define AI_CANONNAME 0x00000002 /* fill ai_canonname */
#define AI_NUMERICHOST 0x00000004 /* prevent host name resolution */
#define AI_NUMERICSERV 0x00000008 /* prevent service name resolution */
+#if defined(_NETBSD_SOURCE)
+#define AI_SRV 0x00000010 /* Do service record lookups */
+/* valid flags for addrinfo (not a standard def, apps should not use it) */
+#define AI_MASK \
+ (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV | \
+ AI_SRV)
+#else
/* valid flags for addrinfo (not a standard def, apps should not use it) */
#define AI_MASK \
(AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | AI_NUMERICSERV)
+#endif
#if 0
/*
Index: lib/libc/net/getaddrinfo.c
===================================================================
RCS file: /cvsroot/src/lib/libc/net/getaddrinfo.c,v
retrieving revision 1.72
diff -u -r1.72 getaddrinfo.c
--- lib/libc/net/getaddrinfo.c 27 May 2004 18:40:07 -0000 1.72
+++ lib/libc/net/getaddrinfo.c 4 Jun 2004 09:11:30 -0000
@@ -100,6 +100,7 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <fcntl.h>
#include <syslog.h>
#include <stdarg.h>
@@ -212,6 +213,13 @@
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 **);
@@ -334,6 +342,43 @@
} while (ai);
}
+#define PERIOD 0x2e
+#define hyphenchar(c) ((c) == 0x2d)
+#define periodchar(c) ((c) == PERIOD)
+#define underschar(c) ((c) == 0x5f)
+#define alphachar(c) (((c) >= 0x41 && (c) <= 0x5a) \
+ || ((c) >= 0x61 && (c) <= 0x7a))
+#define digitchar(c) ((c) >= 0x30 && (c) <= 0x39)
+
+#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 pch = PERIOD, ch = *dn++;
+
+ while (ch != '\0') {
+ int nch = *dn++;
+
+ if (periodchar(ch)) {
+ ;
+ } else 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);
+ }
+ pch = ch, ch = nch;
+ }
+ return (1);
+}
+
static int
str2number(const char *p)
{
@@ -585,7 +630,7 @@
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;
@@ -598,6 +643,18 @@
case NS_SUCCESS:
error = 0;
for (cur = result; cur; cur = cur->ai_next) {
+ /* Check for already filled port. */
+ switch (cur->ai_family)
+ {
+ case AF_INET:
+ if (((struct sockaddr_in*)(void*)cur->ai_addr)->sin_port)
+ continue;
+ break;
+ case AF_INET6:
+ if (((struct sockaddr_in6*)(void*)cur->ai_addr)->sin6_port)
+ continue;
+ break;
+ }
GET_PORT(cur, servname);
/* canonname should be filled already */
}
@@ -1055,7 +1112,7 @@
const struct addrinfo *pai)
{
struct addrinfo sentinel, *cur;
- struct addrinfo ai;
+ struct addrinfo ai, *aip;
const struct afd *afd;
char *canonname;
const HEADER *hp;
@@ -1068,6 +1125,13 @@
char tbuf[MAXDNAME];
int (*name_ok) (const char *);
char hostbuf[8*1024];
+ int port, pri, weight;
+ struct res_target q[2];
+ res_state res;
+ struct srvinfo *srvlist, *srv, *csrv, *tsrv;
+ querybuf *buf[2];
+ int rfd;
+ unsigned int i;
_DIAGASSERT(answer != NULL);
_DIAGASSERT(qname != NULL);
@@ -1084,6 +1148,9 @@
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(); */
}
@@ -1123,6 +1190,7 @@
}
haveanswer = 0;
had_error = 0;
+ srvlist = NULL;
while (ancount-- > 0 && cp < eom && !had_error) {
n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
if ((n < 0) || !(*name_ok)(bp)) {
@@ -1222,17 +1290,219 @@
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, sizeof tbuf);
+ if ((n < 0) || !res_hnok(tbuf))
+ {
+ had_error++;
+ continue;
+ }
+ cp += n;
+ n = strlen(tbuf) + 1; /* for the \0 */
+ if (n >= MAXDNAME)
+ {
+ had_error++;
+ continue;
+ }
+ srv = malloc (sizeof (struct srvinfo));
+ 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)
+ {
+ /* Check for explicit rejection. */
+ if (!srvlist->next && !srvlist->name[0])
+ {
+ free (srvlist);
+ h_errno = HOST_NOT_FOUND;
+ return NULL;
+ }
+
+ /* Shuffle the weights. */
+ csrv = NULL;
+ srv = srvlist;
+ rfd = -1;
+ while (srv)
+ {
+ /* Accumulate weight. */
+ weight = srv->weight;
+ while (srv->next && srv->next->pri == srv->pri)
+ {
+ srv = srv->next;
+ weight += srv->weight;
+ }
+ if (weight && ((!csrv && srv != srvlist) ||
+ (csrv && srv != csrv->next)))
+ {
+ /* There's at least two and nonzero total
+ weight. */
+ i = weight;
+ pri = 0;
+ while (n)
+ {
+ pri = pri << 1 | 1;
+ i >>= 1;
+ }
+ if (rfd < 0)
+ {
+ rfd = open ("/dev/urandom", O_RDONLY);
+ if (rfd < 0)
+ break; /* No random, no shuffle. */
+ }
+ n = pri + 1;
+ while (n > weight)
+ {
+ if (read (rfd, &n, sizeof (n)) < sizeof (n))
+ break;
+ n &= pri;
+ }
+ if (n > weight)
+ break;
+ if (csrv)
+ {
+ srv = csrv;
+ while (srv->next && n > srv->next->weight)
+ {
+ srv = srv->next;
+ n -= srv->weight;
+ }
+ if (srv != csrv)
+ {
+ tsrv = srv->next;
+ srv->next = tsrv->next;
+ tsrv->next = csrv->next;
+ csrv = csrv->next = tsrv;
+ }
+ else
+ csrv = csrv->next;
+ }
+ else if (n > srvlist->weight)
+ {
+ srv = srvlist;
+ n -= srvlist->weight;
+ while (srv->next && n > srv->next->weight)
+ {
+ srv = srv->next;
+ n -= srv->weight;
+ }
+ tsrv = srv->next;
+ srv->next = tsrv->next;
+ tsrv->next = srvlist;
+ csrv = srvlist = tsrv;
+ }
+ else
+ csrv = srvlist;
+ }
+ else
+ csrv = srv;
+ srv = csrv->next;
+ }
+ if (rfd >= 0)
+ close (rfd);
+
+ res = __res_get_state();
+ buf[0] = malloc (sizeof (*buf[0]));
+ buf[1] = malloc (sizeof (*buf[1]));
+ while (srvlist)
+ {
+ srv = srvlist->next;
+
+ if (buf[0] && buf[1])
+ {
+ /* Since res_* doesn't give the additional section
+ we always look up. */
+ memset (q, 0, sizeof (q));
+ q[0].name = srvlist->name;
+ q[0].qclass = C_IN;
+ q[0].qtype = T_AAAA;
+ q[0].answer = buf[0]->buf;
+ q[0].anslen = sizeof(buf[0]->buf);
+ q[0].next = q + 1;
+ q[1].name = srvlist->name;
+ q[1].qclass = C_IN;
+ q[1].qtype = T_A;
+ q[1].answer = buf[1]->buf;
+ q[1].anslen = sizeof(buf[1]->buf);
+
+ if (res_queryN(srvlist->name, q, res) >= 0) {
+ for (n = 0; n < 2; n++)
+ {
+ aip = getanswer(buf[n], q[n].n, q[n].name, q[n].qtype, pai);
+ if (aip) {
+ cur->ai_next = aip;
+ while (cur && cur->ai_next)
+ {
+ cur = cur->ai_next;
+ switch (cur->ai_family)
+ {
+ case AF_INET:
+ ((struct sockaddr_in*)(void*)cur->ai_addr)->sin_port = htons (srvlist->port);
+ break;
+ case AF_INET6:
+ ((struct sockaddr_in6*)(void*)cur->ai_addr)->sin6_port = htons (srvlist->port);
+ break;
+ }
+ haveanswer++;
+ }
+ }
+ }
+ }
+ }
+
+ free (srvlist);
+ srvlist = srv;
+ }
+ if (buf[0])
+ free (buf[0]);
+ if (buf[1])
+ free (buf[1]);
+ }
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)
+ {
+ if (!canonname)
+ (void)get_canonname(pai, sentinel.ai_next, qname);
+ else
+ (void)get_canonname(pai, sentinel.ai_next, canonname);
+ }
h_errno = NETDB_SUCCESS;
return sentinel.ai_next;
}
@@ -1247,13 +1517,20 @@
{
struct addrinfo *ai;
querybuf *buf, *buf2;
- const char *name;
+ const char *name, *servname;
const struct addrinfo *pai;
struct addrinfo sentinel, *cur;
struct res_target q, q2;
+ struct servent *serv;
+ int i, nsrv;
+ const char * const srvprotos[] = { "tcp", "udp" };
+ const int srvnottype[] = { SOCK_DGRAM, SOCK_STREAM };
+ const int nsrvprotos = 2;
+ char *tname;
name = va_arg(ap, char *);
pai = va_arg(ap, const struct addrinfo *);
+ servname = va_arg (ap, char *);
memset(&q, 0, sizeof(q2));
memset(&q2, 0, sizeof(q2));
@@ -1271,56 +1548,104 @@
h_errno = NETDB_INTERNAL;
return NS_NOTFOUND;
}
-
- 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;
- }
- if (res_searchN(name, &q) < 0) {
- 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;
+
+ nsrv = 0;
+ if (servname && (pai->ai_flags & AI_SRV) &&
+ !(pai->ai_flags & AI_NUMERICSERV) &&
+ str2number (servname) == -1)
+ {
+ for (i = 0; i < nsrvprotos; i++)
+ {
+ if (pai->ai_socktype != srvnottype[i])
+ {
+ serv = getservbyname (servname, srvprotos[i]);
+ if (serv)
+ {
+ tname = malloc (strlen (name) + strlen (serv->s_proto) + 2 + strlen (serv->s_name) + 3);
+ if (tname)
+ {
+ /* Construct service DNS name. */
+ strcpy (tname, "_");
+ strcat (tname, serv->s_name);
+ strcat (tname, "._");
+ strcat (tname, serv->s_proto);
+ strcat (tname, ".");
+ strcat (tname, name);
+ q.name = tname;
+ q.qclass = C_IN;
+ q.qtype = T_SRV;
+ q.answer = buf->buf;
+ q.anslen = sizeof (buf->buf);
+ if (res_searchN (q.name, &q) > 0)
+ {
+ nsrv++;
+ 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;
+ }
+ }
+ free (tname);
+ memset (&q, 0, sizeof (q));
+ }
+ }
+ }
+ }
}
- if (q.next) {
- ai = getanswer(buf2, q2.n, q2.name, q2.qtype, pai);
- if (ai)
+
+ if (!nsrv)
+ {
+ 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;
+ }
+ if (res_searchN(name, &q) < 0) {
+ 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);
>Release-Note:
>Audit-Trail:
>Unformatted: