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: