Subject: Adding TTL information to gethostbyname() and friends
To: None <tech-net@netbsd.org>
From: Ian Lance Taylor <ian@airs.com>
List: tech-net
Date: 06/01/2003 18:41:14
One of the long standing drawbacks to the BSD sockets API is that
there is no reasonable way to get the TTL of a DNS entry.

Since many users are on the wrong side of a dialup line from a DNS
cache, programs like browsers cache the results of gethostbyname().
However, they don't know how long to cache them for.  The result is
that they cache most entries for too short a time, and cache some
entries for too long a time.  The former leads to inefficient use of
the dialup line, and the latter means that it is impossible to
seamlessly fail over a web server to a different IP address.

I have appended a patch which adds TTL information to DNS results.

For gethostbyname() and similar functions, the TTL is returned in a
new global variable named h_ttl.  Adding a new global variable
preserves the same calling convention, but of course it would be
possible to instead add new functions--gethostbynamettl() or something
like that.  Also, when and if NetBSD gets an implementation of
gethostbyname_r(), a variant should be written which returns the TTL
directly without using a global variable.

For getaddrinfo(), the TTL is returned in a new field added to the
addrinfo struct.

The patch is reasonably careful to be namespace clean--perhaps too
careful.  Also, the new ai_ttl field inaddrinfo should only be
considered valid if a newly defined bit is set in the ai_flags field,
to avoid confusion if a program is dynamically linked against a
library which does not set the new field.

If the TTL is not defined, as is the case when the answer comes from
/etc/hosts or from NIS, then the returned TTL is set to -1, with
appropriate macros to be used by calling code.

I'm not wedded to this particular API, but I do want to encourage the
NetBSD maintainers to incorporate some way to retrieve the TTL from a
standard DNS lookup.  I do not know of any other standard approach, so
this requires defining a new API.

Is the right place to propose such a patch?

Ian

Index: include/netdb.h
===================================================================
RCS file: /cvsroot/wasabisrc/src/include/netdb.h,v
retrieving revision 1.1.1.4
diff -p -u -r1.1.1.4 netdb.h
--- include/netdb.h	12 May 2003 20:50:58 -0000	1.1.1.4
+++ include/netdb.h	28 May 2003 20:17:30 -0000
@@ -118,6 +118,19 @@ typedef _BSD_SIZE_T_	size_t;
 extern int h_errno;
 
 /*
+ * Time To Live, set by gethostbyaddr(), gethostbyname(),
+ * gethostbyname2(), getnetbyname(), getnetbyaddr().  At least for
+ * now, also set by getnameinfo().
+ */
+extern int32_t __h_ttl;
+#define __H_TTL_UNKNOWN	(-1)
+
+#if defined(_NETBSD_SOURCE)
+#define h_ttl		__h_ttl
+#define H_TTL_UNKNOWN	__H_TTL_UNKNOWN
+#endif
+
+/*
  * Structures returned by network data base library.  All addresses are
  * supplied in host order, and returned in network order (suitable for
  * use in system calls).
@@ -183,6 +196,17 @@ struct addrinfo {
 	char	*ai_canonname;	/* canonical name for hostname */
 	struct sockaddr *ai_addr;	/* binary address */
 	struct addrinfo *ai_next;	/* next structure in linked list */
+	/*
+	 * ai_ttl should only be examined if AI_TTLVALID is set in
+	 * ai_flags, to avoid problems when running against an older
+	 * libc.
+	 */
+	int32_t	__ai_ttl;	/* Time To Live in seconds */
+#define __AI_TTL_UNKNOWN	(-1)
+#if defined(_NETBSD_SOURCE)
+#define ai_ttl	__ai_ttl
+#define AI_TTL_UNKNOWN	__AI_TTL_UNKNOWN
+#endif
 };
 #endif
 
@@ -232,8 +256,12 @@ struct addrinfo {
 #define	AI_PASSIVE	0x00000001 /* get address to use bind() */
 #define	AI_CANONNAME	0x00000002 /* fill ai_canonname */
 #define	AI_NUMERICHOST	0x00000004 /* prevent name resolution */
+#define __AI_TTLVALID	0x00000008 /* ai_ttl field is set in result */
+#if defined(_NETBSD_SOURCE)
+#define AI_TTLVALID	__AI_TTLVALID
+#endif
 /* valid flags for addrinfo */
-#define	AI_MASK		(AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST)
+#define	AI_MASK		(AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST | __AI_TTLVALID)
 #endif
 
 #if (_POSIX_C_SOURCE - 0) >= 200112L || (_XOPEN_SOURCE - 0) >= 520 || \
Index: lib/libc/net/getaddrinfo.3
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/getaddrinfo.3,v
retrieving revision 1.1.1.4
diff -p -u -r1.1.1.4 getaddrinfo.3
--- lib/libc/net/getaddrinfo.3	12 May 2003 21:08:36 -0000	1.1.1.4
+++ lib/libc/net/getaddrinfo.3	28 May 2003 20:17:31 -0000
@@ -82,6 +82,7 @@ struct addrinfo {
      char *    ai_canonname; /* canonical name for nodename */
      struct sockaddr  *ai_addr; /* binary address */
      struct addrinfo  *ai_next; /* next structure in linked list */
+     int32_t   ai_ttl;       /* Time To Live in seconds */
 };
 .Ed
 .Pp
@@ -196,6 +197,30 @@ specified by the
 .Fa ai_addrlen
 member.
 .Pp
+In each returned
+.Li addrinfo
+structure the member
+.Fa ai_ttl
+holds the time to live--the time in seconds for which this entry will
+remain valid.  If the
+.Fa ai_ttl
+member is zero, the returned value may be used, but should not be
+cached.  If the
+.Fa ai_ttl
+member is
+.Dv AI_TTL_UNKNOWN
+(which is defined to be -1) then the time to live is unknown (for
+example, this would be the case if the information comes from the
+.Pa /etc/hosts
+file).  The
+.Fa ai_ttl
+member should only be examined if
+.Dv AI_TTLVALID
+is set in the
+.Fa ai_flags
+member; this avoids confusion if dynamically linked against an older
+version of the standard library.
+.Pp
 If the
 .Dv AI_PASSIVE
 bit is set in the
@@ -600,6 +625,14 @@ function is defined in IEEE POSIX 1003.1
 and documented in
 .Dq Basic Socket Interface Extensions for IPv6
 .Pq RFC2553 .
+The
+.Fa ai_ttl
+member is a NetBSD extension, along with the
+.Dv AI_TTLVALID
+bit in the
+.Fa ai_flags
+member; they were added by Ian Lance Taylor
+.Aq ian@airs.com .
 .\"
 .Sh HISTORY
 The implementation first appeared in WIDE Hydrangea IPv6 protocol stack kit.
Index: lib/libc/net/getaddrinfo.c
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/getaddrinfo.c,v
retrieving revision 1.1.1.5
diff -p -u -r1.1.1.5 getaddrinfo.c
--- lib/libc/net/getaddrinfo.c	24 May 2003 14:50:49 -0000	1.1.1.5
+++ lib/libc/net/getaddrinfo.c	28 May 2003 20:17:32 -0000
@@ -380,7 +380,7 @@ getaddrinfo(hostname, servname, hints, r
 	memset(&sentinel, 0, sizeof(sentinel));
 	cur = &sentinel;
 	pai = &ai;
-	pai->ai_flags = 0;
+	pai->ai_flags = __AI_TTLVALID;
 	pai->ai_family = PF_UNSPEC;
 	pai->ai_socktype = ANY;
 	pai->ai_protocol = ANY;
@@ -388,6 +388,7 @@ getaddrinfo(hostname, servname, hints, r
 	pai->ai_canonname = NULL;
 	pai->ai_addr = NULL;
 	pai->ai_next = NULL;
+	pai->__ai_ttl = __AI_TTL_UNKNOWN;
 	
 	if (hostname == NULL && servname == NULL)
 		return EAI_NONAME;
@@ -409,6 +410,8 @@ getaddrinfo(hostname, servname, hints, r
 			ERR(EAI_FAMILY);
 		}
 		memcpy(pai, hints, sizeof(*pai));
+		pai->ai_flags |= __AI_TTLVALID;
+		pai->__ai_ttl = __AI_TTL_UNKNOWN;
 
 		/*
 		 * if both socktype/protocol are specified, check if they
@@ -1148,6 +1151,7 @@ getanswer(answer, anslen, qname, qtype, 
 	const u_char *eom;
 	char *bp, *ep;
 	int type, class, ancount, qdcount;
+	int32_t ttl;
 	int haveanswer, had_error;
 	char tbuf[MAXDNAME];
 	int (*name_ok) __P((const char *));
@@ -1217,7 +1221,11 @@ getanswer(answer, anslen, qname, qtype, 
 		type = _getshort(cp);
  		cp += INT16SZ;			/* type */
 		class = _getshort(cp);
- 		cp += INT16SZ + INT32SZ;	/* class, TTL */
+ 		cp += INT16SZ;			/* class */
+		ttl = _getlong(cp);
+		if (ttl < 0)
+			ttl = INT32_MAX;
+		cp += INT32SZ;			/* TTL */
 		n = _getshort(cp);
 		cp += INT16SZ;			/* len */
 		if (class != C_IN) {
@@ -1302,8 +1310,10 @@ getanswer(answer, anslen, qname, qtype, 
 			cur->ai_next = get_ai(&ai, afd, (const char *)cp);
 			if (cur->ai_next == NULL)
 				had_error++;
-			while (cur && cur->ai_next)
+			while (cur && cur->ai_next) {
+				cur->ai_next->__ai_ttl = ttl;
 				cur = cur->ai_next;
+			}
 			cp += n;
 			break;
 		default:
Index: lib/libc/net/gethnamaddr.c
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/gethnamaddr.c,v
retrieving revision 1.1.1.4
diff -p -u -r1.1.1.4 gethnamaddr.c
--- lib/libc/net/gethnamaddr.c	24 May 2003 14:50:50 -0000	1.1.1.4
+++ lib/libc/net/gethnamaddr.c	28 May 2003 20:17:33 -0000
@@ -107,6 +107,8 @@ __weak_alias(gethostbyname,_gethostbynam
 #define	MAXALIASES	35
 #define	MAXADDRS	35
 
+int32_t __h_ttl;
+
 static const char AskedForGot[] =
 			  "gethostby*.getanswer: asked for \"%s\", got \"%s\"";
 
@@ -227,6 +229,7 @@ getanswer(answer, anslen, qname, qtype)
 	char *bp, **ap, **hap, *ep;
 	int type, class, ancount, qdcount;
 	int haveanswer, had_error;
+	int32_t ttl, answerttl;
 	int toobig = 0;
 	char tbuf[MAXDNAME];
 	const char *tname;
@@ -291,6 +294,7 @@ getanswer(answer, anslen, qname, qtype)
 	*hap = NULL;
 	host.h_addr_list = h_addr_ptrs;
 	haveanswer = 0;
+	answerttl = 0;
 	had_error = 0;
 	while (ancount-- > 0 && cp < eom && !had_error) {
 		n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
@@ -303,7 +307,11 @@ getanswer(answer, anslen, qname, qtype)
 		type = _getshort(cp);
  		cp += INT16SZ;			/* type */
 		class = _getshort(cp);
- 		cp += INT16SZ + INT32SZ;	/* class, TTL */
+ 		cp += INT16SZ;			/* class */
+		ttl = _getlong(cp);
+		if (ttl < 0)
+			ttl = INT32_MAX;
+		cp += INT32SZ;			/* TTL */
 		n = _getshort(cp);
 		cp += INT16SZ;			/* len */
 		BOUNDS_CHECK(cp, n);
@@ -422,6 +430,7 @@ getanswer(answer, anslen, qname, qtype)
 				map_v4v6_hostent(&host, &bp, ep);
 			}
 			h_errno = NETDB_SUCCESS;
+			__h_ttl = ttl;
 			return (&host);
 #endif
 		case T_A:
@@ -478,8 +487,11 @@ getanswer(answer, anslen, qname, qtype)
 		default:
 			abort();
 		}
-		if (!had_error)
+		if (!had_error) {
+			if (!haveanswer || ttl < answerttl)
+				answerttl = ttl;
 			haveanswer++;
+		}
 	}
 	if (haveanswer) {
 		*ap = NULL;
@@ -504,6 +516,7 @@ getanswer(answer, anslen, qname, qtype)
 		if (_res.options & RES_USE_INET6)
 			map_v4v6_hostent(&host, &bp, ep);
 		h_errno = NETDB_SUCCESS;
+		__h_ttl = answerttl;
 		return (&host);
 	}
  no_recovery:
@@ -549,6 +562,8 @@ gethostbyname2(name, af)
 
 	_DIAGASSERT(name != NULL);
 
+	__h_ttl = __H_TTL_UNKNOWN;
+
 	switch (af) {
 	case AF_INET:
 		size = INADDRSZ;
@@ -669,6 +684,8 @@ gethostbyaddr(addr, len, af)
 	};
 	
 	_DIAGASSERT(addr != NULL);
+
+	__h_ttl = __H_TTL_UNKNOWN;
 
 	if (af == AF_INET6 && len == IN6ADDRSZ &&
 	    (IN6_IS_ADDR_LINKLOCAL((const struct in6_addr *)(const void *)uaddr) ||
Index: lib/libc/net/gethostbyname.3
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/gethostbyname.3,v
retrieving revision 1.1.1.4
diff -p -u -r1.1.1.4 gethostbyname.3
--- lib/libc/net/gethostbyname.3	17 Apr 2003 05:21:43 -0000	1.1.1.4
+++ lib/libc/net/gethostbyname.3	28 May 2003 20:17:33 -0000
@@ -50,6 +50,7 @@
 .Sh SYNOPSIS
 .In netdb.h
 .Fd extern int h_errno;
+.Fd extern int32_t h_ttl;
 .Ft struct hostent *
 .Fn gethostbyname "const char *name"
 .Ft struct hostent *
@@ -192,6 +193,25 @@ function returns a string which is the m
 value of the
 .Fa err
 parameter.
+.Pp
+On a successful return, the functions
+.Fn gethostbyname ,
+.Fn gethostbyname2 ,
+and
+.Fn gethostbyaddr
+will set the global variable
+.Va h_ttl
+to the time to live--the time in seconds for which the returned value
+will remain valid.  If
+.Va h_ttl
+is zero, the returned value may be used, but should not be cached.  If
+.Va h_ttl
+is
+.Dv H_TTL_UNKNOWN
+(which is defined to be -1) then the time to live is unknown (for
+example, this would be the case if the information comes from the
+.Pa /etc/hosts
+file).
 .Sh FILES
 .Bl -tag -width /etc/hosts -compact
 .It Pa /etc/hosts
@@ -290,7 +310,11 @@ functions appeared in
 The
 .Fn gethostbyname2
 function first appeared in bind-4.9.4.
-IPv6 support was implemented in WIDE Hydrangea IPv6 protocol stack kit.
+IPv6 support was implemented in WIDE Hydrangea IPv6 protocol stack
+kit.  The
+.Va h_ttl
+variable is a NetBSD extension added by Ian Lance Taylor
+.Aq ian@airs.com .
 .Sh BUGS
 These functions use static data storage;
 if the data is needed for future use, it should be
Index: lib/libc/net/getnameinfo.c
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/getnameinfo.c,v
retrieving revision 1.1.1.3
diff -p -u -r1.1.1.3 getnameinfo.c
--- lib/libc/net/getnameinfo.c	27 Jan 2003 03:08:27 -0000	1.1.1.3
+++ lib/libc/net/getnameinfo.c	28 May 2003 20:17:33 -0000
@@ -116,6 +116,7 @@ getnameinfo(sa, salen, host, hostlen, se
 	socklen_t hostlen, servlen;
 	int flags;
 {
+	__h_ttl = __H_TTL_UNKNOWN;
 
 	switch (sa->sa_family) {
 	case AF_INET:
Index: lib/libc/net/getnetent.3
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/getnetent.3,v
retrieving revision 1.1.1.3
diff -p -u -r1.1.1.3 getnetent.3
--- lib/libc/net/getnetent.3	17 Apr 2003 05:21:44 -0000	1.1.1.3
+++ lib/libc/net/getnetent.3	28 May 2003 20:17:33 -0000
@@ -123,6 +123,24 @@ or until
 .Dv EOF
 is encountered.
 Network numbers are supplied in host order.
+.Pp
+On a successful return, the functions
+.Fn getnetbyname
+and
+.Fn getnetbyaddr
+will set the global variable
+.Va h_ttl
+to the time to live--the time in seconds for which the returned value
+will remain valid.  If
+.Va h_ttl
+is zero, the returned value may be used, but should not be cached.  If
+.Va h_ttl
+is
+.Dv H_TTL_UNKNOWN
+(which is defined to be -1) then the time to live is unknown (for
+example, this would be the case if the information comes from the
+.Pa /etc/networks
+file, as opposed to a DNS lookup).
 .Sh FILES
 .Bl -tag -width /etc/networks -compact
 .It Pa /etc/networks
@@ -145,6 +163,10 @@ and
 .Fn endnetent
 functions appeared in
 .Bx 4.2 .
+The
+.Va h_ttl
+variable is a NetBSD extension added by Ian Lance Taylor
+.Aq ian@airs.com .
 .Sh BUGS
 The data space used by
 these functions is static; if future use requires the data, it should be
Index: lib/libc/net/getnetnamadr.c
===================================================================
RCS file: /cvsroot/wasabisrc/src/lib/libc/net/getnetnamadr.c,v
retrieving revision 1.1.1.3
diff -p -u -r1.1.1.3 getnetnamadr.c
--- lib/libc/net/getnetnamadr.c	27 Jan 2003 03:08:27 -0000	1.1.1.3
+++ lib/libc/net/getnetnamadr.c	28 May 2003 20:17:33 -0000
@@ -130,6 +130,7 @@ getnetanswer(answer, anslen, net_i)
 	int n;
 	u_char *eom;
 	int type, class, ancount, qdcount, haveanswer, i, nchar;
+	int32_t ttl, answerttl;
 	char aux1[MAXDNAME], aux2[MAXDNAME], ans[MAXDNAME];
 	char *in, *st, *pauxt, *bp, **ap;
 	char *paux1 = &aux1[0], *paux2 = &aux2[0], *ep;
@@ -177,6 +178,7 @@ getnetanswer(answer, anslen, net_i)
 	*ap = NULL;
 	net_entry.n_aliases = net_aliases;
 	haveanswer = 0;
+	answerttl = 0;
 	while (--ancount >= 0 && cp < eom) {
 		n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
 		if ((n < 0) || !res_dnok(bp))
@@ -186,7 +188,9 @@ getnetanswer(answer, anslen, net_i)
 		(void)strlcpy(ans, bp, sizeof(ans));
 		GETSHORT(type, cp);
 		GETSHORT(class, cp);
-		cp += INT32SZ;		/* TTL */
+		GETLONG(ttl, cp);
+		if (ttl < 0)
+			ttl = INT32_MAX;
 		GETSHORT(n, cp);
 		if (class == C_IN && type == T_PTR) {
 			n = dn_expand(answer->buf, eom, cp, bp, ep - bp);
@@ -199,6 +203,8 @@ getnetanswer(answer, anslen, net_i)
 			bp += strlen(bp) + 1;
 			net_entry.n_addrtype =
 				(class == C_IN) ? AF_INET : AF_UNSPEC;
+			if (!haveanswer || ttl < answerttl)
+				answerttl = ttl;
 			haveanswer++;
 		}
 	}
@@ -241,6 +247,7 @@ getnetanswer(answer, anslen, net_i)
 		if (strcasecmp(in, "IN-ADDR.ARPA") != 0)
 			goto next_alias;
 		net_entry.n_aliases++;
+		__h_ttl = answerttl;
 		return (&net_entry);
 	}
 	h_errno = TRY_AGAIN;
@@ -369,6 +376,8 @@ getnetbyaddr(net, net_type)
 		{ 0 }
 	};
 
+	__h_ttl = __H_TTL_UNKNOWN;
+
 	if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
 		h_errno = NETDB_INTERNAL;
 		return (NULL);
@@ -470,6 +479,8 @@ getnetbyname(net)
 	};
 
 	_DIAGASSERT(net != NULL);
+
+	__h_ttl = __H_TTL_UNKNOWN;
 
 	if ((_res.options & RES_INIT) == 0 && res_init() == -1) {
 		h_errno = NETDB_INTERNAL;