about summary refs log tree commit diff
path: root/resolv
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2017-06-30 21:10:23 +0200
committerFlorian Weimer <fweimer@redhat.com>2017-07-03 20:52:59 +0200
commit352f4ff9a268b81ef5d4b2413f582565806e4790 (patch)
treefb27056dfdeafe43c021f6127c9544c016e78019 /resolv
parent4e45d83c92dbb5b8dc20654f32395108d18cf739 (diff)
downloadglibc-352f4ff9a268b81ef5d4b2413f582565806e4790.tar.gz
glibc-352f4ff9a268b81ef5d4b2413f582565806e4790.tar.xz
glibc-352f4ff9a268b81ef5d4b2413f582565806e4790.zip
resolv: Introduce struct resolv_context [BZ #21668]
struct resolv_context objects provide a temporary resolver context
which does not change during a name lookup operation.  Only when the
outmost context is created, the stub resolver configuration is
verified to be current (at present, only against previous res_init
calls).  Subsequent attempts to obtain the context will reuse the
result of the initial verification operation.

struct resolv_context can also be extended in the future to store
data which needs to be deallocated during thread cancellation.
Diffstat (limited to 'resolv')
-rw-r--r--resolv/Makefile3
-rw-r--r--resolv/Versions10
-rw-r--r--resolv/compat-gethnamaddr.c95
-rw-r--r--resolv/nss_dns/dns-canon.c19
-rw-r--r--resolv/nss_dns/dns-host.c104
-rw-r--r--resolv/nss_dns/dns-network.c21
-rw-r--r--resolv/res-close.c3
-rw-r--r--resolv/res_libc.c31
-rw-r--r--resolv/res_mkquery.c92
-rw-r--r--resolv/res_query.c298
-rw-r--r--resolv/res_send.c44
-rw-r--r--resolv/res_use_inet6.h49
-rw-r--r--resolv/resolv-internal.h36
-rw-r--r--resolv/resolv_context.c201
-rw-r--r--resolv/resolv_context.h95
15 files changed, 810 insertions, 291 deletions
diff --git a/resolv/Makefile b/resolv/Makefile
index bab1ac24a6..126da0736a 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -28,7 +28,8 @@ headers	:= resolv.h bits/types/res_state.h \
 	   sys/bitypes.h
 
 routines := herror inet_addr inet_ntop inet_pton nsap_addr res_init \
-	    res_hconf res_libc res-state res_randomid res-close
+	    res_hconf res_libc res-state res_randomid res-close \
+	    resolv_context
 
 tests = tst-aton tst-leaks tst-inet_ntop
 xtests = tst-leaks2
diff --git a/resolv/Versions b/resolv/Versions
index f528ed51e8..b05778d965 100644
--- a/resolv/Versions
+++ b/resolv/Versions
@@ -26,8 +26,12 @@ libc {
 
     __h_errno; __resp;
 
-    __res_maybe_init; __res_iclose;
+    __res_iclose;
     __inet_pton_length;
+    __resolv_context_get;
+    __resolv_context_get_preinit;
+    __resolv_context_get_override;
+    __resolv_context_put;
   }
 }
 
@@ -79,7 +83,9 @@ libresolv {
     # Needed in libnss_dns.
     __ns_name_unpack; __ns_name_ntop;
     __ns_get16; __ns_get32;
-    __libc_res_nquery; __libc_res_nsearch;
+    __res_context_query;
+    __res_context_search;
+    __res_context_hostalias;
   }
 }
 
diff --git a/resolv/compat-gethnamaddr.c b/resolv/compat-gethnamaddr.c
index 813c7d4e85..259378b2be 100644
--- a/resolv/compat-gethnamaddr.c
+++ b/resolv/compat-gethnamaddr.c
@@ -67,6 +67,7 @@
 # include <stdio.h>
 # include <netdb.h>
 # include <resolv/resolv-internal.h>
+# include <resolv/resolv_context.h>
 # include <ctype.h>
 # include <errno.h>
 # include <stdlib.h>
@@ -84,6 +85,9 @@ static u_char host_addr[16];	/* IPv4 or IPv6 */
 static FILE *hostf = NULL;
 static int stayopen = 0;
 
+static struct hostent *res_gethostbyname2_context (struct resolv_context *,
+						   const char *name, int af);
+
 static void map_v4v6_address (const char *src, char *dst) __THROW;
 static void map_v4v6_hostent (struct hostent *hp, char **bp, int *len) __THROW;
 
@@ -428,23 +432,31 @@ libresolv_hidden_proto (res_gethostbyname2)
 struct hostent *
 res_gethostbyname (const char *name)
 {
-	struct hostent *hp;
-
-	if (__res_maybe_init (&_res, 0) == -1) {
-		__set_h_errno (NETDB_INTERNAL);
-		return (NULL);
-	}
-	if (res_use_inet6 ()) {
-		hp = res_gethostbyname2(name, AF_INET6);
-		if (hp)
-			return (hp);
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      __set_h_errno (NETDB_INTERNAL);
+      return NULL;
+    }
+
+  if (res_use_inet6 ())
+    {
+      struct hostent *hp = res_gethostbyname2_context (ctx, name, AF_INET6);
+      if (hp != NULL)
+	{
+	  __resolv_context_put (ctx);
+	  return hp;
 	}
-	return (res_gethostbyname2(name, AF_INET));
+    }
+  struct hostent *hp = res_gethostbyname2_context (ctx, name, AF_INET);
+  __resolv_context_put (ctx);
+  return hp;
 }
 compat_symbol (libresolv, res_gethostbyname, res_gethostbyname, GLIBC_2_0);
 
-struct hostent *
-res_gethostbyname2 (const char *name, int af)
+static struct hostent *
+res_gethostbyname2_context (struct resolv_context *ctx,
+			    const char *name, int af)
 {
 	union
 	{
@@ -457,11 +469,6 @@ res_gethostbyname2 (const char *name, int af)
 	int n, size, type, len;
 	struct hostent *ret;
 
-	if (__res_maybe_init (&_res, 0) == -1) {
-		__set_h_errno (NETDB_INTERNAL);
-		return (NULL);
-	}
-
 	switch (af) {
 	case AF_INET:
 		size = INADDRSZ;
@@ -485,8 +492,10 @@ res_gethostbyname2 (const char *name, int af)
 	 * this is also done in res_query() since we are not the only
 	 * function that looks up host names.
 	 */
-	if (!strchr(name, '.') && (cp = __hostalias(name)))
-		name = cp;
+	char abuf[MAXDNAME];
+	if (strchr (name, '.') != NULL
+	    && (cp = __res_context_hostalias (ctx, name, abuf, sizeof (abuf))))
+	  name = cp;
 
 	/*
 	 * disallow names consisting only of digits/dots, unless
@@ -558,8 +567,9 @@ res_gethostbyname2 (const char *name, int af)
 
 	buf.buf = origbuf = (querybuf *) alloca (1024);
 
-	if ((n = __libc_res_nsearch(&_res, name, C_IN, type, buf.buf->buf, 1024,
-				    &buf.ptr, NULL, NULL, NULL, NULL)) < 0) {
+	if ((n = __res_context_search
+	     (ctx, name, C_IN, type, buf.buf->buf, 1024,
+	      &buf.ptr, NULL, NULL, NULL, NULL)) < 0) {
 		if (buf.buf != origbuf)
 			free (buf.buf);
 		Dprintf("res_nsearch failed (%d)\n", n);
@@ -572,11 +582,26 @@ res_gethostbyname2 (const char *name, int af)
 		free (buf.buf);
 	return ret;
 }
+
+struct hostent *
+res_gethostbyname2 (const char *name, int af)
+{
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      __set_h_errno (NETDB_INTERNAL);
+      return NULL;
+    }
+  struct hostent *hp = res_gethostbyname2_context (ctx, name, AF_INET);
+  __resolv_context_put (ctx);
+  return hp;
+}
 libresolv_hidden_def (res_gethostbyname2)
 compat_symbol (libresolv, res_gethostbyname2, res_gethostbyname2, GLIBC_2_0);
 
-struct hostent *
-res_gethostbyaddr (const void *addr, socklen_t len, int af)
+static struct hostent *
+res_gethostbyaddr_context (struct resolv_context *ctx,
+			   const void *addr, socklen_t len, int af)
 {
 	const u_char *uaddr = (const u_char *)addr;
 	static const u_char mapped[] = { 0,0, 0,0, 0,0, 0,0, 0,0, 0xff,0xff };
@@ -592,10 +617,6 @@ res_gethostbyaddr (const void *addr, socklen_t len, int af)
 	struct hostent *hp;
 	char qbuf[MAXDNAME+1], *qp = NULL;
 
-	if (__res_maybe_init (&_res, 0) == -1) {
-		__set_h_errno (NETDB_INTERNAL);
-		return (NULL);
-	}
 	if (af == AF_INET6 && len == IN6ADDRSZ &&
 	    (!memcmp(uaddr, mapped, sizeof mapped) ||
 	     !memcmp(uaddr, tunnelled, sizeof tunnelled))) {
@@ -645,8 +666,8 @@ res_gethostbyaddr (const void *addr, socklen_t len, int af)
 
 	buf.buf = orig_buf = (querybuf *) alloca (1024);
 
-	n = __libc_res_nquery(&_res, qbuf, C_IN, T_PTR, buf.buf->buf, 1024,
-			      &buf.ptr, NULL, NULL, NULL, NULL);
+	n = __res_context_query (ctx, qbuf, C_IN, T_PTR, buf.buf->buf, 1024,
+				 &buf.ptr, NULL, NULL, NULL, NULL);
 	if (n < 0) {
 		if (buf.buf != orig_buf)
 			free (buf.buf);
@@ -673,6 +694,20 @@ res_gethostbyaddr (const void *addr, socklen_t len, int af)
 	__set_h_errno (NETDB_SUCCESS);
 	return (hp);
 }
+
+struct hostent *
+res_gethostbyaddr (const void *addr, socklen_t len, int af)
+{
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      __set_h_errno (NETDB_INTERNAL);
+      return NULL;
+    }
+  struct hostent *hp = res_gethostbyaddr_context (ctx, addr, len, af);
+  __resolv_context_put (ctx);
+  return hp;
+}
 compat_symbol (libresolv, res_gethostbyaddr, res_gethostbyaddr, GLIBC_2_0);
 
 void
diff --git a/resolv/nss_dns/dns-canon.c b/resolv/nss_dns/dns-canon.c
index 4276eb6542..7a5c39dc20 100644
--- a/resolv/nss_dns/dns-canon.c
+++ b/resolv/nss_dns/dns-canon.c
@@ -23,7 +23,8 @@
 #include <stdint.h>
 #include <arpa/nameser.h>
 #include <nsswitch.h>
-
+#include <resolv/resolv_context.h>
+#include <resolv/resolv-internal.h>
 
 #if PACKETSZ > 65536
 # define MAXPACKET	PACKETSZ
@@ -58,11 +59,19 @@ _nss_dns_getcanonname_r (const char *name, char *buffer, size_t buflen,
   } ansp = { .ptr = buf };
   enum nss_status status = NSS_STATUS_UNAVAIL;
 
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
+
   for (int i = 0; i < nqtypes; ++i)
     {
-      int r = __libc_res_nquery (&_res, name, ns_c_in, qtypes[i],
-				 buf, sizeof (buf), &ansp.ptr, NULL, NULL,
-				 NULL, NULL);
+      int r = __res_context_query (ctx, name, ns_c_in, qtypes[i],
+				   buf, sizeof (buf), &ansp.ptr, NULL, NULL,
+				   NULL, NULL);
       if (r > 0)
 	{
 	  /* We need to decode the response.  Just one question record.
@@ -168,6 +177,6 @@ _nss_dns_getcanonname_r (const char *name, char *buffer, size_t buflen,
 
   if (ansp.ptr != buf)
     free (ansp.ptr);
-
+  __resolv_context_put (ctx);
   return status;
 }
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 206924de86..9d7ceb1691 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -84,6 +84,7 @@
 
 /* Get implementeation for some internal functions.  */
 #include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 #include <resolv/mapv4v6addr.h>
 #include <resolv/mapv4v6hostent.h>
 
@@ -121,13 +122,13 @@ static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
 				       int *errnop, int *h_errnop,
 				       int32_t *ttlp);
 
-extern enum nss_status _nss_dns_gethostbyname3_r (const char *name, int af,
-						  struct hostent *result,
-						  char *buffer, size_t buflen,
-						  int *errnop, int *h_errnop,
-						  int32_t *ttlp,
-						  char **canonp);
-hidden_proto (_nss_dns_gethostbyname3_r)
+static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
+					       const char *name, int af,
+					       struct hostent *result,
+					       char *buffer, size_t buflen,
+					       int *errnop, int *h_errnop,
+					       int32_t *ttlp,
+					       char **canonp);
 
 /* Return the expected RDATA length for an address record type (A or
    AAAA).  */
@@ -145,11 +146,31 @@ rrtype_to_rdata_length (int type)
     }
 }
 
+
 enum nss_status
 _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
 			   char *buffer, size_t buflen, int *errnop,
 			   int *h_errnop, int32_t *ttlp, char **canonp)
 {
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
+  enum nss_status status = gethostbyname3_context
+    (ctx, name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp);
+  __resolv_context_put (ctx);
+  return status;
+}
+
+static enum nss_status
+gethostbyname3_context (struct resolv_context *ctx,
+			const char *name, int af, struct hostent *result,
+			char *buffer, size_t buflen, int *errnop,
+			int *h_errnop, int32_t *ttlp, char **canonp)
+{
   union
   {
     querybuf *buf;
@@ -163,13 +184,6 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
   int olderr = errno;
   enum nss_status status;
 
-  if (__res_maybe_init (&_res, 0) == -1)
-    {
-      *errnop = errno;
-      *h_errnop = NETDB_INTERNAL;
-      return NSS_STATUS_UNAVAIL;
-    }
-
   switch (af) {
   case AF_INET:
     size = INADDRSZ;
@@ -194,13 +208,13 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
    * function that looks up host names.
    */
   if (strchr (name, '.') == NULL
-      && (cp = res_hostalias (&_res, name, tmp, sizeof (tmp))) != NULL)
+      && (cp = __res_context_hostalias (ctx, name, tmp, sizeof (tmp))) != NULL)
     name = cp;
 
   host_buffer.buf = orig_host_buffer = (querybuf *) alloca (1024);
 
-  n = __libc_res_nsearch (&_res, name, C_IN, type, host_buffer.buf->buf,
-			  1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
+  n = __res_context_search (ctx, name, C_IN, type, host_buffer.buf->buf,
+			    1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0)
     {
       switch (errno)
@@ -232,10 +246,10 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
 	 by having the RES_USE_INET6 bit in _res.options set, we try
 	 another lookup.  */
       if (af == AF_INET6 && res_use_inet6 ())
-	n = __libc_res_nsearch (&_res, name, C_IN, T_A, host_buffer.buf->buf,
-				host_buffer.buf != orig_host_buffer
-				? MAXPACKET : 1024, &host_buffer.ptr,
-				NULL, NULL, NULL, NULL);
+	n = __res_context_search (ctx, name, C_IN, T_A, host_buffer.buf->buf,
+				  host_buffer.buf != orig_host_buffer
+				  ? MAXPACKET : 1024, &host_buffer.ptr,
+				  NULL, NULL, NULL, NULL);
 
       if (n < 0)
 	{
@@ -256,8 +270,6 @@ _nss_dns_gethostbyname3_r (const char *name, int af, struct hostent *result,
     free (host_buffer.buf);
   return status;
 }
-hidden_def (_nss_dns_gethostbyname3_r)
-
 
 enum nss_status
 _nss_dns_gethostbyname2_r (const char *name, int af, struct hostent *result,
@@ -274,15 +286,21 @@ _nss_dns_gethostbyname_r (const char *name, struct hostent *result,
 			  char *buffer, size_t buflen, int *errnop,
 			  int *h_errnop)
 {
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
+    {
+      *errnop = errno;
+      *h_errnop = NETDB_INTERNAL;
+      return NSS_STATUS_UNAVAIL;
+    }
   enum nss_status status = NSS_STATUS_NOTFOUND;
-
   if (res_use_inet6 ())
-    status = _nss_dns_gethostbyname3_r (name, AF_INET6, result, buffer,
-					buflen, errnop, h_errnop, NULL, NULL);
+    status = gethostbyname3_context (ctx, name, AF_INET6, result, buffer,
+				     buflen, errnop, h_errnop, NULL, NULL);
   if (status == NSS_STATUS_NOTFOUND)
-    status = _nss_dns_gethostbyname3_r (name, AF_INET, result, buffer,
-					buflen, errnop, h_errnop, NULL, NULL);
-
+    status = gethostbyname3_context (ctx, name, AF_INET, result, buffer,
+				     buflen, errnop, h_errnop, NULL, NULL);
+  __resolv_context_put (ctx);
   return status;
 }
 
@@ -292,7 +310,8 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
 			   char *buffer, size_t buflen, int *errnop,
 			   int *herrnop, int32_t *ttlp)
 {
-  if (__res_maybe_init (&_res, 0) == -1)
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
     {
       *errnop = errno;
       *herrnop = NETDB_INTERNAL;
@@ -307,7 +326,7 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
   if (strchr (name, '.') == NULL)
     {
       char *tmp = alloca (NS_MAXDNAME);
-      const char *cp = res_hostalias (&_res, name, tmp, NS_MAXDNAME);
+      const char *cp = __res_context_hostalias (ctx, name, tmp, NS_MAXDNAME);
       if (cp != NULL)
 	name = cp;
     }
@@ -326,9 +345,9 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
 
   int olderr = errno;
   enum nss_status status;
-  int n = __libc_res_nsearch (&_res, name, C_IN, T_QUERY_A_AND_AAAA,
-			      host_buffer.buf->buf, 2048, &host_buffer.ptr,
-			      &ans2p, &nans2p, &resplen2, &ans2p_malloced);
+  int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
+				host_buffer.buf->buf, 2048, &host_buffer.ptr,
+				&ans2p, &nans2p, &resplen2, &ans2p_malloced);
   if (n >= 0)
     {
       status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
@@ -371,6 +390,7 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
   if (host_buffer.buf != orig_host_buffer)
     free (host_buffer.buf);
 
+  __resolv_context_put (ctx);
   return status;
 }
 
@@ -423,7 +443,8 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
 
  host_data = (struct host_data *) buffer;
 
-  if (__res_maybe_init (&_res, 0) == -1)
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
     {
       *errnop = errno;
       *h_errnop = NETDB_INTERNAL;
@@ -453,12 +474,14 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
     default:
       *errnop = EAFNOSUPPORT;
       *h_errnop = NETDB_INTERNAL;
+      __resolv_context_put (ctx);
       return NSS_STATUS_UNAVAIL;
     }
   if (size > len)
     {
       *errnop = EAFNOSUPPORT;
       *h_errnop = NETDB_INTERNAL;
+      __resolv_context_put (ctx);
       return NSS_STATUS_UNAVAIL;
     }
 
@@ -487,14 +510,15 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
       break;
     }
 
-  n = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR, host_buffer.buf->buf,
-			 1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
+  n = __res_context_query (ctx, qbuf, C_IN, T_PTR, host_buffer.buf->buf,
+			   1024, &host_buffer.ptr, NULL, NULL, NULL, NULL);
   if (n < 0)
     {
       *h_errnop = h_errno;
       __set_errno (olderr);
       if (host_buffer.buf != orig_host_buffer)
 	free (host_buffer.buf);
+      __resolv_context_put (ctx);
       return errno == ECONNREFUSED ? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
     }
 
@@ -503,7 +527,10 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
   if (host_buffer.buf != orig_host_buffer)
     free (host_buffer.buf);
   if (status != NSS_STATUS_SUCCESS)
-    return status;
+    {
+      __resolv_context_put (ctx);
+      return status;
+    }
 
   result->h_addrtype = af;
   result->h_length = len;
@@ -511,6 +538,7 @@ _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
   host_data->h_addr_ptrs[0] = (char *) host_data->host_addr;
   host_data->h_addr_ptrs[1] = NULL;
   *h_errnop = NETDB_SUCCESS;
+  __resolv_context_put (ctx);
   return NSS_STATUS_SUCCESS;
 }
 hidden_def (_nss_dns_gethostbyaddr2_r)
diff --git a/resolv/nss_dns/dns-network.c b/resolv/nss_dns/dns-network.c
index dc1599b471..f190eb2225 100644
--- a/resolv/nss_dns/dns-network.c
+++ b/resolv/nss_dns/dns-network.c
@@ -67,6 +67,8 @@
 #include "nsswitch.h"
 #include <arpa/inet.h>
 #include <arpa/nameser.h>
+#include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 
 /* Maximum number of aliases we allow.  */
 #define MAX_NR_ALIASES	48
@@ -115,7 +117,8 @@ _nss_dns_getnetbyname_r (const char *name, struct netent *result,
   int anslen;
   enum nss_status status;
 
-  if (__res_maybe_init (&_res, 0) == -1)
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
     {
       *errnop = errno;
       *herrnop = NETDB_INTERNAL;
@@ -124,14 +127,16 @@ _nss_dns_getnetbyname_r (const char *name, struct netent *result,
 
   net_buffer.buf = orig_net_buffer = (querybuf *) alloca (1024);
 
-  anslen = __libc_res_nsearch (&_res, name, C_IN, T_PTR, net_buffer.buf->buf,
-			       1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
+  anslen = __res_context_search
+    (ctx, name, C_IN, T_PTR, net_buffer.buf->buf,
+     1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
   if (anslen < 0)
     {
       /* Nothing found.  */
       *errnop = errno;
       if (net_buffer.buf != orig_net_buffer)
 	free (net_buffer.buf);
+      __resolv_context_put (ctx);
       return (errno == ECONNREFUSED
 	      || errno == EPFNOSUPPORT
 	      || errno == EAFNOSUPPORT)
@@ -142,6 +147,7 @@ _nss_dns_getnetbyname_r (const char *name, struct netent *result,
 			errnop, herrnop, BYNAME);
   if (net_buffer.buf != orig_net_buffer)
     free (net_buffer.buf);
+  __resolv_context_put (ctx);
   return status;
 }
 
@@ -169,7 +175,8 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
   if (type != AF_INET)
     return NSS_STATUS_UNAVAIL;
 
-  if (__res_maybe_init (&_res, 0) == -1)
+  struct resolv_context *ctx = __resolv_context_get ();
+  if (ctx == NULL)
     {
       *errnop = errno;
       *herrnop = NETDB_INTERNAL;
@@ -204,8 +211,8 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
 
   net_buffer.buf = orig_net_buffer = (querybuf *) alloca (1024);
 
-  anslen = __libc_res_nquery (&_res, qbuf, C_IN, T_PTR, net_buffer.buf->buf,
-			      1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
+  anslen = __res_context_query (ctx, qbuf, C_IN, T_PTR, net_buffer.buf->buf,
+				1024, &net_buffer.ptr, NULL, NULL, NULL, NULL);
   if (anslen < 0)
     {
       /* Nothing found.  */
@@ -213,6 +220,7 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
       __set_errno (olderr);
       if (net_buffer.buf != orig_net_buffer)
 	free (net_buffer.buf);
+      __resolv_context_put (ctx);
       return (err == ECONNREFUSED
 	      || err == EPFNOSUPPORT
 	      || err == EAFNOSUPPORT)
@@ -233,6 +241,7 @@ _nss_dns_getnetbyaddr_r (uint32_t net, int type, struct netent *result,
       result->n_net = u_net;
     }
 
+  __resolv_context_put (ctx);
   return status;
 }
 
diff --git a/resolv/res-close.c b/resolv/res-close.c
index 73f18d1525..97da73c99c 100644
--- a/resolv/res-close.c
+++ b/resolv/res-close.c
@@ -83,6 +83,7 @@
  */
 
 #include <resolv-internal.h>
+#include <resolv_context.h>
 #include <not-cancel.h>
 
 /* Close all open sockets.  If FREE_ADDR is true, deallocate any
@@ -124,6 +125,8 @@ libc_hidden_def (__res_nclose)
 static void __attribute__ ((section ("__libc_thread_freeres_fn")))
 res_thread_freeres (void)
 {
+  __resolv_context_freeres ();
+
   if (_res.nscount == 0)
     /* Never called res_ninit.  */
     return;
diff --git a/resolv/res_libc.c b/resolv/res_libc.c
index 3d7b4f72d0..5066983ccf 100644
--- a/resolv/res_libc.c
+++ b/resolv/res_libc.c
@@ -98,37 +98,6 @@ res_init (void)
 
   return __res_vinit (&_res, 1);
 }
-
-/* Initialize *RESP if RES_INIT is not yet set in RESP->options, or if
-   res_init in some other thread requested re-initializing.  */
-int
-__res_maybe_init (res_state resp, int preinit)
-{
-  if (resp->options & RES_INIT)
-    {
-      if (__res_initstamp != resp->_u._ext.initstamp)
-        {
-          if (resp->nscount > 0)
-            __res_iclose (resp, true);
-          return __res_vinit (resp, 1);
-        }
-      return 0;
-    }
-  else if (preinit)
-    {
-      if (!resp->retrans)
-        resp->retrans = RES_TIMEOUT;
-      if (!resp->retry)
-        resp->retry = RES_DFLRETRY;
-      resp->options = RES_DEFAULT;
-      if (!resp->id)
-        resp->id = res_randomid ();
-      return __res_vinit (resp, 1);
-    }
-  else
-    return __res_ninit (resp);
-}
-libc_hidden_def (__res_maybe_init)
 
 /* This needs to be after the use of _res in res_init, above.  */
 #undef _res
diff --git a/resolv/res_mkquery.c b/resolv/res_mkquery.c
index 9afb410980..59fc5ab28c 100644
--- a/resolv/res_mkquery.c
+++ b/resolv/res_mkquery.c
@@ -88,6 +88,7 @@
 #include <arpa/nameser.h>
 #include <netdb.h>
 #include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 #include <string.h>
 #include <sys/time.h>
 #include <shlib-compat.h>
@@ -98,22 +99,10 @@
 # define RANDOM_BITS(Var) { uint64_t v64; HP_TIMING_NOW (v64); Var = v64; }
 #endif
 
-/* Form all types of queries.  Returns the size of the result or -1 on
-   error.
-
-   STATP points to an initialized resolver state.  OP is the opcode of
-   the query.  DNAME is the domain.  CLASS and TYPE are the DNS query
-   class and type.  DATA can be NULL; otherwise, it is a pointer to a
-   domain name which is included in the generated packet (if op ==
-   NS_NOTIFY_OP).  BUF must point to the out buffer of BUFLEN bytes.
-
-   DATALEN and NEWRR_IN are currently ignored.  */
 int
-res_nmkquery (res_state statp, int op, const char *dname,
-              int class, int type,
-              const unsigned char *data, int datalen,
-              const unsigned char *newrr_in,
-              unsigned char *buf, int buflen)
+__res_context_mkquery (struct resolv_context *ctx, int op, const char *dname,
+                       int class, int type, const unsigned char *data,
+                       unsigned char *buf, int buflen)
 {
   HEADER *hp;
   unsigned char *cp;
@@ -132,22 +121,17 @@ res_nmkquery (res_state statp, int op, const char *dname,
      by one after the initial randomization which still predictable if
      the application does multiple requests.  */
   int randombits;
-  do
-    {
 #ifdef RANDOM_BITS
-      RANDOM_BITS (randombits);
+  RANDOM_BITS (randombits);
 #else
-      struct timeval tv;
-      __gettimeofday (&tv, NULL);
-      randombits = (tv.tv_sec << 8) ^ tv.tv_usec;
+  struct timeval tv;
+  __gettimeofday (&tv, NULL);
+  randombits = (tv.tv_sec << 8) ^ tv.tv_usec;
 #endif
-    }
-  while ((randombits & 0xffff) == 0);
 
-  statp->id = (statp->id + randombits) & 0xffff;
-  hp->id = statp->id;
+  hp->id = randombits;
   hp->opcode = op;
-  hp->rd = (statp->options & RES_RECURSE) != 0;
+  hp->rd = (ctx->resp->options & RES_RECURSE) != 0;
   hp->rcode = NOERROR;
   cp = buf + HFIXEDSZ;
   buflen -= HFIXEDSZ;
@@ -201,7 +185,45 @@ res_nmkquery (res_state statp, int op, const char *dname,
     }
   return cp - buf;
 }
-libresolv_hidden_def (res_nmkquery)
+
+/* Common part of res_nmkquery and res_mkquery.  */
+static int
+context_mkquery_common (struct resolv_context *ctx,
+                        int op, const char *dname, int class, int type,
+                        const unsigned char *data,
+                        unsigned char *buf, int buflen)
+{
+  if (ctx == NULL)
+    return -1;
+  int result = __res_context_mkquery
+    (ctx, op, dname, class, type, data, buf, buflen);
+  if (result >= 2)
+    memcpy (&ctx->resp->id, buf, 2);
+  __resolv_context_put (ctx);
+  return result;
+}
+
+/* Form all types of queries.  Returns the size of the result or -1 on
+   error.
+
+   STATP points to an initialized resolver state.  OP is the opcode of
+   the query.  DNAME is the domain.  CLASS and TYPE are the DNS query
+   class and type.  DATA can be NULL; otherwise, it is a pointer to a
+   domain name which is included in the generated packet (if op ==
+   NS_NOTIFY_OP).  BUF must point to the out buffer of BUFLEN bytes.
+
+   DATALEN and NEWRR_IN are currently ignored.  */
+int
+res_nmkquery (res_state statp, int op, const char *dname,
+              int class, int type,
+              const unsigned char *data, int datalen,
+              const unsigned char *newrr_in,
+              unsigned char *buf, int buflen)
+{
+  return context_mkquery_common
+    (__resolv_context_get_override (statp),
+     op, dname, class, type, data, buf, buflen);
+}
 
 int
 res_mkquery (int op, const char *dname, int class, int type,
@@ -209,13 +231,9 @@ res_mkquery (int op, const char *dname, int class, int type,
              const unsigned char *newrr_in,
              unsigned char *buf, int buflen)
 {
-  if (__res_maybe_init (&_res, 1) == -1)
-    {
-      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
-      return -1;
-    }
-  return res_nmkquery (&_res, op, dname, class, type,
-                       data, datalen, newrr_in, buf, buflen);
+  return context_mkquery_common
+    (__resolv_context_get_preinit (),
+     op, dname, class, type, data, buf, buflen);
 }
 
 /* Create an OPT resource record.  Return the length of the final
@@ -227,8 +245,8 @@ res_mkquery (int op, const char *dname, int class, int type,
    pointers to must be BUFLEN bytes long.  ANSLEN is the advertised
    EDNS buffer size (to be included in the OPT resource record).  */
 int
-__res_nopt (res_state statp, int n0, unsigned char *buf, int buflen,
-            int anslen)
+__res_nopt (struct resolv_context *ctx,
+            int n0, unsigned char *buf, int buflen, int anslen)
 {
   uint16_t flags = 0;
   HEADER *hp = (HEADER *) buf;
@@ -269,7 +287,7 @@ __res_nopt (res_state statp, int n0, unsigned char *buf, int buflen,
   *cp++ = NOERROR;              /* Extended RCODE.  */
   *cp++ = 0;                    /* EDNS version.  */
 
-  if (statp->options & RES_USE_DNSSEC)
+  if (ctx->resp->options & RES_USE_DNSSEC)
     flags |= NS_OPT_DNSSEC_OK;
 
   NS_PUT16 (flags, cp);
diff --git a/resolv/res_query.c b/resolv/res_query.c
index 760bf324e8..33249e36f5 100644
--- a/resolv/res_query.c
+++ b/resolv/res_query.c
@@ -75,6 +75,7 @@
 #include <netdb.h>
 #include <resolv.h>
 #include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -89,33 +90,28 @@
 #define QUERYSIZE	(HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1)
 
 static int
-__libc_res_nquerydomain(res_state statp, const char *name, const char *domain,
-			int class, int type, u_char *answer, int anslen,
-			u_char **answerp, u_char **answerp2, int *nanswerp2,
-			int *resplen2, int *answerp2_malloced);
-
-/*
- * Formulate a normal query, send, and await answer.
- * Returned answer is placed in supplied buffer "answer".
- * Perform preliminary check of answer, returning success only
- * if no error is indicated and the answer count is nonzero.
- * Return the size of the response on success, -1 on error.
- * Error number is left in H_ERRNO.
- *
- * Caller must parse answer and determine whether it answers the question.
- */
+__res_context_querydomain (struct resolv_context *,
+			   const char *name, const char *domain,
+			   int class, int type, unsigned char *answer, int anslen,
+			   unsigned char **answerp, unsigned char **answerp2, int *nanswerp2,
+			   int *resplen2, int *answerp2_malloced);
+
+/* Formulate a normal query, send, and await answer.  Returned answer
+   is placed in supplied buffer ANSWER.  Perform preliminary check of
+   answer, returning success only if no error is indicated and the
+   answer count is nonzero.  Return the size of the response on
+   success, -1 on error.  Error number is left in h_errno.
+
+   Caller must parse answer and determine whether it answers the
+   question.  */
 int
-__libc_res_nquery(res_state statp,
-		  const char *name,	/* domain name */
-		  int class, int type,	/* class and type of query */
-		  u_char *answer,	/* buffer to put answer */
-		  int anslen,		/* size of answer buffer */
-		  u_char **answerp,	/* if buffer needs to be enlarged */
-		  u_char **answerp2,
-		  int *nanswerp2,
-		  int *resplen2,
-		  int *answerp2_malloced)
+__res_context_query (struct resolv_context *ctx, const char *name,
+		     int class, int type,
+		     unsigned char *answer, int anslen,
+		     unsigned char **answerp, unsigned char **answerp2,
+		     int *nanswerp2, int *resplen2, int *answerp2_malloced)
 {
+	struct __res_state *statp = ctx->resp;
 	HEADER *hp = (HEADER *) answer;
 	HEADER *hp2;
 	int n, use_malloc = 0;
@@ -132,15 +128,15 @@ __libc_res_nquery(res_state statp,
 
 	if (type == T_QUERY_A_AND_AAAA)
 	  {
-	    n = res_nmkquery(statp, QUERY, name, class, T_A, NULL, 0, NULL,
-			     query1, bufsize);
+	    n = __res_context_mkquery (ctx, QUERY, name, class, T_A, NULL,
+				       query1, bufsize);
 	    if (n > 0)
 	      {
 		if ((statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
 		  {
 		    /* Use RESOLV_EDNS_BUFFER_SIZE because the receive
 		       buffer can be reallocated.  */
-		    n = __res_nopt (statp, n, query1, bufsize,
+		    n = __res_nopt (ctx, n, query1, bufsize,
 				    RESOLV_EDNS_BUFFER_SIZE);
 		    if (n < 0)
 		      goto unspec_nomem;
@@ -157,13 +153,13 @@ __libc_res_nquery(res_state statp,
 		  }
 		int nused = n + npad;
 		query2 = buf + nused;
-		n = res_nmkquery(statp, QUERY, name, class, T_AAAA, NULL, 0,
-				 NULL, query2, bufsize - nused);
+		n = __res_context_mkquery (ctx, QUERY, name, class, T_AAAA,
+					   NULL, query2, bufsize - nused);
 		if (n > 0
 		    && (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
 		  /* Use RESOLV_EDNS_BUFFER_SIZE because the receive
 		     buffer can be reallocated.  */
-		  n = __res_nopt (statp, n, query2, bufsize,
+		  n = __res_nopt (ctx, n, query2, bufsize,
 				  RESOLV_EDNS_BUFFER_SIZE);
 		nquery2 = n;
 	      }
@@ -172,8 +168,8 @@ __libc_res_nquery(res_state statp,
 	  }
 	else
 	  {
-	    n = res_nmkquery(statp, QUERY, name, class, type, NULL, 0, NULL,
-			     query1, bufsize);
+	    n = __res_context_mkquery (ctx, QUERY, name, class, type, NULL,
+				       query1, bufsize);
 
 	    if (n > 0
 		&& (statp->options & (RES_USE_EDNS0|RES_USE_DNSSEC)) != 0)
@@ -185,7 +181,7 @@ __libc_res_nquery(res_state statp,
 		  advertise = anslen;
 		else
 		  advertise = RESOLV_EDNS_BUFFER_SIZE;
-		n = __res_nopt (statp, n, query1, bufsize, advertise);
+		n = __res_nopt (ctx, n, query1, bufsize, advertise);
 	      }
 
 	    nquery1 = n;
@@ -209,9 +205,9 @@ __libc_res_nquery(res_state statp,
 		return (n);
 	}
 	assert (answerp == NULL || (void *) *answerp == (void *) answer);
-	n = __libc_res_nsend(statp, query1, nquery1, query2, nquery2, answer,
-			     anslen, answerp, answerp2, nanswerp2, resplen2,
-			     answerp2_malloced);
+	n = __res_context_send (ctx, query1, nquery1, query2, nquery2, answer,
+				anslen, answerp, answerp2, nanswerp2, resplen2,
+				answerp2_malloced);
 	if (use_malloc)
 		free (buf);
 	if (n < 0) {
@@ -220,7 +216,7 @@ __libc_res_nquery(res_state statp,
 	}
 
 	if (answerp != NULL)
-	  /* __libc_res_nsend might have reallocated the buffer.  */
+	  /* __res_context_send might have reallocated the buffer.  */
 	  hp = (HEADER *) *answerp;
 
 	/* We simplify the following tests by assigning HP to HP2 or
@@ -280,7 +276,24 @@ __libc_res_nquery(res_state statp,
  success:
 	return (n);
 }
-libresolv_hidden_def (__libc_res_nquery)
+libresolv_hidden_def (__res_context_query)
+
+/* Common part of res_nquery and res_query.  */
+static int
+context_query_common (struct resolv_context *ctx,
+		      const char *name, int class, int type,
+		      unsigned char *answer, int anslen)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return -1;
+    }
+  int result = __res_context_query (ctx, name, class, type, answer, anslen,
+				    NULL, NULL, NULL, NULL, NULL);
+  __resolv_context_put (ctx);
+  return result;
+}
 
 int
 res_nquery(res_state statp,
@@ -289,41 +302,30 @@ res_nquery(res_state statp,
 	   u_char *answer,	/* buffer to put answer */
 	   int anslen)		/* size of answer buffer */
 {
-	return __libc_res_nquery(statp, name, class, type, answer, anslen,
-				 NULL, NULL, NULL, NULL, NULL);
+  return context_query_common
+    (__resolv_context_get_override (statp), name, class, type, answer, anslen);
 }
-libresolv_hidden_def (res_nquery)
 
 int
 res_query (const char *name, int class, int type,
 	   unsigned char *answer, int anslen)
 {
-  if (__res_maybe_init (&_res, 1) == -1)
-    {
-      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
-      return -1;
-    }
-  return res_nquery (&_res, name, class, type, answer, anslen);
+  return context_query_common
+    (__resolv_context_get (), name, class, type, answer, anslen);
 }
 
-/*
- * Formulate a normal query, send, and retrieve answer in supplied buffer.
- * Return the size of the response on success, -1 on error.
- * If enabled, implement search rules until answer or unrecoverable failure
- * is detected.  Error code, if any, is left in H_ERRNO.
- */
+/* Formulate a normal query, send, and retrieve answer in supplied
+   buffer.  Return the size of the response on success, -1 on error.
+   If enabled, implement search rules until answer or unrecoverable
+   failure is detected.  Error code, if any, is left in h_errno.  */
 int
-__libc_res_nsearch(res_state statp,
-		   const char *name,	/* domain name */
-		   int class, int type,	/* class and type of query */
-		   u_char *answer,	/* buffer to put answer */
-		   int anslen,		/* size of answer */
-		   u_char **answerp,
-		   u_char **answerp2,
-		   int *nanswerp2,
-		   int *resplen2,
-		   int *answerp2_malloced)
+__res_context_search (struct resolv_context *ctx,
+		      const char *name, int class, int type,
+		      unsigned char *answer, int anslen,
+		      unsigned char **answerp, unsigned char **answerp2,
+		      int *nanswerp2, int *resplen2, int *answerp2_malloced)
 {
+	struct __res_state *statp = ctx->resp;
 	const char *cp, * const *domain;
 	HEADER *hp = (HEADER *) answer;
 	char tmp[NS_MAXDNAME];
@@ -344,10 +346,11 @@ __libc_res_nsearch(res_state statp,
 		trailing_dot++;
 
 	/* If there aren't any dots, it could be a user-level alias. */
-	if (!dots && (cp = res_hostalias(statp, name, tmp, sizeof tmp))!= NULL)
-		return (__libc_res_nquery(statp, cp, class, type, answer,
-					  anslen, answerp, answerp2,
-					  nanswerp2, resplen2, answerp2_malloced));
+	if (!dots && (cp = __res_context_hostalias
+		      (ctx, name, tmp, sizeof tmp))!= NULL)
+	  return __res_context_query (ctx, cp, class, type, answer,
+				      anslen, answerp, answerp2,
+				      nanswerp2, resplen2, answerp2_malloced);
 
 	/*
 	 * If there are enough dots in the name, let's just give it a
@@ -356,10 +359,10 @@ __libc_res_nsearch(res_state statp,
 	 */
 	saved_herrno = -1;
 	if (dots >= statp->ndots || trailing_dot) {
-		ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
-					      answer, anslen, answerp,
-					      answerp2, nanswerp2, resplen2,
-					      answerp2_malloced);
+		ret = __res_context_querydomain (ctx, name, NULL, class, type,
+						 answer, anslen, answerp,
+						 answerp2, nanswerp2, resplen2,
+						 answerp2_malloced);
 		if (ret > 0 || trailing_dot
 		    /* If the second response is valid then we use that.  */
 		    || (ret == 0 && resplen2 != NULL && *resplen2 > 0))
@@ -395,7 +398,7 @@ __libc_res_nsearch(res_state statp,
 			const char *dname = domain[0];
 			searched = 1;
 
-			/* __libc_res_nquerydoman concatenates name
+			/* __res_context_querydoman concatenates name
 			   with dname with a "." in between.  If we
 			   pass it in dname the "." we got from the
 			   configured default search path, we'll end
@@ -409,11 +412,10 @@ __libc_res_nsearch(res_state statp,
 			if (dname[0] == '\0')
 				root_on_list++;
 
-			ret = __libc_res_nquerydomain(statp, name, dname,
-						      class, type,
-						      answer, anslen, answerp,
-						      answerp2, nanswerp2,
-						      resplen2, answerp2_malloced);
+			ret = __res_context_querydomain
+			  (ctx, name, dname, class, type,
+			   answer, anslen, answerp, answerp2, nanswerp2,
+			   resplen2, answerp2_malloced);
 			if (ret > 0 || (ret == 0 && resplen2 != NULL
 					&& *resplen2 > 0))
 				return (ret);
@@ -481,10 +483,10 @@ __libc_res_nsearch(res_state statp,
 	 */
 	if ((dots || !searched || (statp->options & RES_NOTLDQUERY) == 0)
 	    && !(tried_as_is || root_on_list)) {
-		ret = __libc_res_nquerydomain(statp, name, NULL, class, type,
-					      answer, anslen, answerp,
-					      answerp2, nanswerp2, resplen2,
-					      answerp2_malloced);
+		ret = __res_context_querydomain
+		  (ctx, name, NULL, class, type,
+		   answer, anslen, answerp, answerp2, nanswerp2,
+		   resplen2, answerp2_malloced);
 		if (ret > 0 || (ret == 0 && resplen2 != NULL
 				&& *resplen2 > 0))
 			return (ret);
@@ -512,7 +514,24 @@ __libc_res_nsearch(res_state statp,
 		RES_SET_H_ERRNO(statp, TRY_AGAIN);
 	return (-1);
 }
-libresolv_hidden_def (__libc_res_nsearch)
+libresolv_hidden_def (__res_context_search)
+
+/* Common part of res_nsearch and res_search.  */
+static int
+context_search_common (struct resolv_context *ctx,
+		       const char *name, int class, int type,
+		       unsigned char *answer, int anslen)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return -1;
+    }
+  int result = __res_context_search (ctx, name, class, type, answer, anslen,
+				     NULL, NULL, NULL, NULL, NULL);
+  __resolv_context_put (ctx);
+  return result;
+}
 
 int
 res_nsearch(res_state statp,
@@ -521,40 +540,30 @@ res_nsearch(res_state statp,
 	    u_char *answer,	/* buffer to put answer */
 	    int anslen)		/* size of answer */
 {
-	return __libc_res_nsearch(statp, name, class, type, answer,
-				  anslen, NULL, NULL, NULL, NULL, NULL);
+  return context_search_common
+    (__resolv_context_get_override (statp), name, class, type, answer, anslen);
 }
-libresolv_hidden_def (res_nsearch)
 
 int
 res_search (const char *name, int class, int type,
 	    unsigned char *answer, int anslen)
 {
-  if (__res_maybe_init (&_res, 1) == -1)
-    {
-      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
-      return -1;
-    }
-
-  return res_nsearch (&_res, name, class, type, answer, anslen);
+  return context_search_common
+    (__resolv_context_get (), name, class, type, answer, anslen);
 }
 
-/*
- * Perform a call on res_query on the concatenation of name and domain.
- */
+/*  Perform a call on res_query on the concatenation of name and
+    domain.  */
 static int
-__libc_res_nquerydomain(res_state statp,
-			const char *name,
-			const char *domain,
-			int class, int type,	/* class and type of query */
-			u_char *answer,		/* buffer to put answer */
-			int anslen,			/* size of answer */
-			u_char **answerp,
-			u_char **answerp2,
-			int *nanswerp2,
-			int *resplen2,
-			int *answerp2_malloced)
+__res_context_querydomain (struct resolv_context *ctx,
+			   const char *name, const char *domain,
+			   int class, int type,
+			   unsigned char *answer, int anslen,
+			   unsigned char **answerp, unsigned char **answerp2,
+			   int *nanswerp2, int *resplen2,
+			   int *answerp2_malloced)
 {
+	struct __res_state *statp = ctx->resp;
 	char nbuf[MAXDNAME];
 	const char *longname = nbuf;
 	size_t n, d;
@@ -580,9 +589,28 @@ __libc_res_nquerydomain(res_state statp,
 		}
 		sprintf(nbuf, "%s.%s", name, domain);
 	}
-	return (__libc_res_nquery(statp, longname, class, type, answer,
-				  anslen, answerp, answerp2, nanswerp2,
-				  resplen2, answerp2_malloced));
+	return __res_context_query (ctx, longname, class, type, answer,
+				    anslen, answerp, answerp2, nanswerp2,
+				    resplen2, answerp2_malloced);
+}
+
+/* Common part of res_nquerydomain and res_querydomain.  */
+static int
+context_querydomain_common (struct resolv_context *ctx,
+			    const char *name, const char *domain,
+			    int class, int type,
+			    unsigned char *answer, int anslen)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return -1;
+    }
+  int result = __res_context_querydomain (ctx, name, domain, class, type,
+					  answer, anslen,
+					  NULL, NULL, NULL, NULL, NULL);
+  __resolv_context_put (ctx);
+  return result;
 }
 
 int
@@ -593,32 +621,28 @@ res_nquerydomain(res_state statp,
 	    u_char *answer,		/* buffer to put answer */
 	    int anslen)		/* size of answer */
 {
-	return __libc_res_nquerydomain(statp, name, domain, class, type,
-				       answer, anslen, NULL, NULL, NULL, NULL,
-				       NULL);
+  return context_querydomain_common
+    (__resolv_context_get_override (statp),
+     name, domain, class, type, answer, anslen);
 }
-libresolv_hidden_def (res_nquerydomain)
 
 int
 res_querydomain (const char *name, const char *domain, int class, int type,
 		 unsigned char *answer, int anslen)
 {
-  if (__res_maybe_init (&_res, 1) == -1)
-    {
-      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
-      return -1;
-    }
-
-  return res_nquerydomain (&_res, name, domain, class, type, answer, anslen);
+  return context_querydomain_common
+    (__resolv_context_get (), name, domain, class, type, answer, anslen);
 }
 
 const char *
-res_hostalias(const res_state statp, const char *name, char *dst, size_t siz) {
+__res_context_hostalias (struct resolv_context *ctx,
+			 const char *name, char *dst, size_t siz)
+{
 	char *file, *cp1, *cp2;
 	char buf[BUFSIZ];
 	FILE *fp;
 
-	if (statp->options & RES_NOALIASES)
+	if (ctx->resp->options & RES_NOALIASES)
 		return (NULL);
 	file = getenv("HOSTALIASES");
 	if (file == NULL || (fp = fopen(file, "rce")) == NULL)
@@ -648,15 +672,37 @@ res_hostalias(const res_state statp, const char *name, char *dst, size_t siz) {
 	fclose(fp);
 	return (NULL);
 }
-libresolv_hidden_def (res_hostalias)
+libresolv_hidden_def (__res_context_hostalias)
+
+/* Common part of res_hostalias and hostalias.  */
+static const char *
+context_hostalias_common (struct resolv_context *ctx,
+			  const char *name, char *dst, size_t siz)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return NULL;
+    }
+  const char *result = __res_context_hostalias (ctx, name, dst, siz);
+  __resolv_context_put (ctx);
+  return result;
+}
+
+const char *
+res_hostalias (res_state statp, const char *name, char *dst, size_t siz)
+{
+  return context_hostalias_common
+    (__resolv_context_get_override (statp), name, dst, siz);
+}
 
 const char *
 hostalias (const char *name)
 {
   static char abuf[MAXDNAME];
-  return res_hostalias (&_res, name, abuf, sizeof abuf);
+  return context_hostalias_common
+    (__resolv_context_get (), name, abuf, sizeof (abuf));
 }
-libresolv_hidden_def (hostalias)
 
 #if SHLIB_COMPAT (libresolv, GLIBC_2_0, GLIBC_2_2)
 # undef res_query
diff --git a/resolv/res_send.c b/resolv/res_send.c
index a7daae8a06..b396aae03c 100644
--- a/resolv/res_send.c
+++ b/resolv/res_send.c
@@ -102,6 +102,7 @@
 #include <fcntl.h>
 #include <netdb.h>
 #include <resolv/resolv-internal.h>
+#include <resolv/resolv_context.h>
 #include <signal.h>
 #include <stdlib.h>
 #include <string.h>
@@ -400,11 +401,14 @@ res_queriesmatch(const u_char *buf1, const u_char *eom1,
 libresolv_hidden_def (res_queriesmatch)
 
 int
-__libc_res_nsend(res_state statp, const u_char *buf, int buflen,
-		 const u_char *buf2, int buflen2,
-		 u_char *ans, int anssiz, u_char **ansp, u_char **ansp2,
-		 int *nansp2, int *resplen2, int *ansp2_malloced)
+__res_context_send (struct resolv_context *ctx,
+		    const unsigned char *buf, int buflen,
+		    const unsigned char *buf2, int buflen2,
+		    unsigned char *ans, int anssiz,
+		    unsigned char **ansp, unsigned char **ansp2,
+		    int *nansp2, int *resplen2, int *ansp2_malloced)
 {
+	struct __res_state *statp = ctx->resp;
 	int gotsomewhere, terrno, try, v_circuit, resplen, n;
 
 	if (statp->nscount == 0) {
@@ -541,22 +545,36 @@ __libc_res_nsend(res_state statp, const u_char *buf, int buflen,
 	return (-1);
 }
 
+/* Common part of res_nsend and res_send.  */
+static int
+context_send_common (struct resolv_context *ctx,
+		     const unsigned char *buf, int buflen,
+		     unsigned char *ans, int anssiz)
+{
+  if (ctx == NULL)
+    {
+      RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
+      return -1;
+    }
+  int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
+				   NULL, NULL, NULL, NULL, NULL);
+  __resolv_context_put (ctx);
+  return result;
+}
+
 int
-res_nsend(res_state statp,
-	  const u_char *buf, int buflen, u_char *ans, int anssiz)
+res_nsend (res_state statp, const unsigned char *buf, int buflen,
+	   unsigned char *ans, int anssiz)
 {
-  return __libc_res_nsend(statp, buf, buflen, NULL, 0, ans, anssiz,
-			  NULL, NULL, NULL, NULL, NULL);
+  return context_send_common
+    (__resolv_context_get_override (statp), buf, buflen, ans, anssiz);
 }
-libresolv_hidden_def (res_nsend)
 
 int
 res_send (const unsigned char *buf, int buflen, unsigned char *ans, int anssiz)
 {
-  if (__res_maybe_init (&_res, 1) == -1)
-    /* errno should have been set by res_init in this case.  */
-    return -1;
-  return res_nsend (&_res, buf, buflen, ans, anssiz);
+  return context_send_common
+    (__resolv_context_get (), buf, buflen, ans, anssiz);
 }
 
 /* Private */
diff --git a/resolv/res_use_inet6.h b/resolv/res_use_inet6.h
new file mode 100644
index 0000000000..8649833072
--- /dev/null
+++ b/resolv/res_use_inet6.h
@@ -0,0 +1,49 @@
+/* Support functions for handling RES_USE_INET6 in getaddrinfo/nscd.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef _RES_USE_INET6_H
+#define _RES_USE_INET6_H
+
+#include <resolv/resolv_context.h>
+#include <resolv/resolv-internal.h>
+
+/* Ensure that RES_USE_INET6 is disabled in *CTX.  Return true if
+   __resolv_context_enable_inet6 below should enable RES_USE_INET6
+   again.  */
+static inline bool
+__resolv_context_disable_inet6 (struct resolv_context *ctx)
+{
+  if (ctx != NULL && ctx->resp->options & DEPRECATED_RES_USE_INET6)
+    {
+      ctx->resp->options &= ~DEPRECATED_RES_USE_INET6;
+      return true;
+    }
+  else
+    return false;
+}
+
+/* If ENABLE, re-enable RES_USE_INET6 in *CTX.  To be paired with
+   __resolv_context_disable_inet6.  */
+static inline void
+__resolv_context_enable_inet6 (struct resolv_context *ctx, bool enable)
+{
+  if (ctx != NULL && enable)
+    ctx->resp->options |= DEPRECATED_RES_USE_INET6;
+}
+
+#endif
diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
index 5a9faf8de9..9246497196 100644
--- a/resolv/resolv-internal.h
+++ b/resolv/resolv-internal.h
@@ -52,9 +52,41 @@ enum
     RESOLV_EDNS_BUFFER_SIZE = 1200,
   };
 
+struct resolv_context;
+
+/* Internal function for implementing res_nmkquery and res_mkquery.
+   Also used by __res_context_query.  */
+int __res_context_mkquery (struct resolv_context *, int op, const char *dname,
+                           int class, int type, const unsigned char *data,
+                           unsigned char *buf, int buflen) attribute_hidden;
+
+/* Main resolver query function for use within glibc.  */
+int __res_context_search (struct resolv_context *, const char *, int, int,
+                          unsigned char *, int, unsigned char **,
+                          unsigned char **, int *, int *, int *);
+libresolv_hidden_proto (__res_context_search)
+
+/* Main resolver query function for use within glibc.  */
+int __res_context_query (struct resolv_context *, const char *, int, int,
+                         unsigned char *, int, unsigned char **,
+                         unsigned char **, int *, int *, int *);
+libresolv_hidden_proto (__res_context_query)
+
+/* Internal function used to implement the query and search
+   functions.  */
+int __res_context_send (struct resolv_context *, const unsigned char *, int,
+                        const unsigned char *, int, unsigned char *,
+                        int, unsigned char **, unsigned char **,
+                        int *, int *, int *) attribute_hidden;
+
+/* Internal function similar to res_hostalias.  */
+const char *__res_context_hostalias (struct resolv_context *,
+                                     const char *, char *, size_t);
+libresolv_hidden_proto (__res_context_hostalias);
+
 /* Add an OPT record to a DNS query.  */
-int __res_nopt (res_state, int n0, unsigned char *buf, int buflen,
-                int anslen) attribute_hidden;
+int __res_nopt (struct resolv_context *, int n0,
+                unsigned char *buf, int buflen, int anslen) attribute_hidden;
 
 /* Convert from presentation format (which usually means ASCII
    printable) to network format (which is usually some kind of binary
diff --git a/resolv/resolv_context.c b/resolv/resolv_context.c
new file mode 100644
index 0000000000..5083a40419
--- /dev/null
+++ b/resolv/resolv_context.c
@@ -0,0 +1,201 @@
+/* Temporary, thread-local resolver state.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#include <resolv_context.h>
+#include <resolv-internal.h>
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+/* Currently active struct resolv_context object.  This pointer forms
+   the start of a single-linked list, using the __next member of
+   struct resolv_context.  This list serves two purposes:
+
+   (a) A subsequent call to __resolv_context_get will only increment
+       the reference counter and will not allocate a new object.  The
+       _res state freshness check is skipped in this case, too.
+
+   (b) The per-thread cleanup function defined by the resolver calls
+       __resolv_context_freeres, which will deallocate all the context
+       objects.  This avoids the need for cancellation handlers and
+       the complexity they bring, but it requires heap allocation of
+       the context object because the per-thread cleanup functions run
+       only after the stack has been fully unwound (and all on-stack
+       objects have been deallocated at this point).
+
+   The TLS variable current is updated even in
+   __resolv_context_get_override, to support case (b) above.  This does
+   not override the per-thread resolver state (as obtained by the
+   non-res_state function such as __resolv_context_get) in an
+   observable way because the wrapped context is only used to
+   implement the res_n* functions in the resolver, and those do not
+   call back into user code which could indirectly use the per-thread
+   resolver state.  */
+static __thread struct resolv_context *current attribute_tls_model_ie;
+
+/* Initialize *RESP if RES_INIT is not yet set in RESP->options, or if
+   res_init in some other thread requested re-initializing.  */
+static __attribute__ ((warn_unused_result)) bool
+maybe_init (struct __res_state *resp, bool preinit)
+{
+  if (resp->options & RES_INIT)
+    {
+      if (__res_initstamp != resp->_u._ext.initstamp)
+        {
+          if (resp->nscount > 0)
+            __res_iclose (resp, true);
+          return __res_vinit (resp, 1) == 0;
+        }
+      return true;
+    }
+
+  if (preinit)
+    {
+      if (!resp->retrans)
+        resp->retrans = RES_TIMEOUT;
+      if (!resp->retry)
+        resp->retry = RES_DFLRETRY;
+      resp->options = RES_DEFAULT;
+      if (!resp->id)
+        resp->id = res_randomid ();
+    }
+  return __res_vinit (resp, preinit) == 0;
+}
+
+/* Allocate a new context object and initialize it.  The object is put
+   on the current list.  */
+static struct resolv_context *
+context_alloc (struct __res_state *resp)
+{
+  struct resolv_context *ctx = malloc (sizeof (*ctx));
+  if (ctx == NULL)
+    return NULL;
+  ctx->resp = resp;
+  ctx->__refcount = 1;
+  ctx->__from_res = true;
+  ctx->__next = current;
+  current = ctx;
+  return ctx;
+}
+
+/* Deallocate the context object and all the state within.   */
+static void
+context_free (struct resolv_context *ctx)
+{
+  current = ctx->__next;
+  free (ctx);
+}
+
+/* Reuse the current context object.  */
+static struct resolv_context *
+context_reuse (void)
+{
+  /* A context object created by __resolv_context_get_override cannot
+     be reused.  */
+  assert (current->__from_res);
+
+  ++current->__refcount;
+
+  /* Check for reference counter wraparound.  This can only happen if
+     the get/put functions are not properly paired.  */
+  assert (current->__refcount > 0);
+
+  return current;
+}
+
+/* Backing function for the __resolv_context_get family of
+   functions.  */
+static struct resolv_context *
+context_get (bool preinit)
+{
+  if (current != NULL)
+    return context_reuse ();
+
+  struct resolv_context *ctx = context_alloc (&_res);
+  if (ctx == NULL)
+    return NULL;
+  if (!maybe_init (ctx->resp, preinit))
+    {
+      context_free (ctx);
+      return NULL;
+    }
+  return ctx;
+}
+
+struct resolv_context *
+__resolv_context_get (void)
+{
+  return context_get (false);
+}
+libc_hidden_def (__resolv_context_get)
+
+struct resolv_context *
+__resolv_context_get_preinit (void)
+{
+  return context_get (true);
+}
+libc_hidden_def (__resolv_context_get_preinit)
+
+struct resolv_context *
+__resolv_context_get_override (struct __res_state *resp)
+{
+  /* NB: As explained asbove, context_alloc will put the context on
+     the current list.  */
+  struct resolv_context *ctx = context_alloc (resp);
+  if (ctx == NULL)
+    return NULL;
+
+  ctx->__from_res = false;
+  return ctx;
+}
+libc_hidden_def (__resolv_context_get_override)
+
+void
+__resolv_context_put (struct resolv_context *ctx)
+{
+  if (ctx == NULL)
+    return;
+
+  /* NB: Callers assume that this function preserves errno and
+     h_errno.  */
+
+  assert (current == ctx);
+  assert (ctx->__refcount > 0);
+
+  if (ctx->__from_res && --ctx->__refcount > 0)
+    /* Do not pop this context yet.  */
+    return;
+
+  context_free (ctx);
+}
+libc_hidden_def (__resolv_context_put)
+
+void
+__resolv_context_freeres (void)
+{
+  /* Deallocate the entire chain of context objects.  */
+  struct resolv_context *ctx = current;
+  current = NULL;
+  while (ctx != NULL)
+    {
+      struct resolv_context *next = ctx->__next;
+      context_free (ctx);
+      ctx = next;
+    }
+}
diff --git a/resolv/resolv_context.h b/resolv/resolv_context.h
new file mode 100644
index 0000000000..27c8d56b36
--- /dev/null
+++ b/resolv/resolv_context.h
@@ -0,0 +1,95 @@
+/* Temporary, thread-local resolver state.
+   Copyright (C) 2017 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+/* struct resolv_context objects are allocated on the heap,
+   initialized by __resolv_context_get (and its variants), and
+   destroyed by __resolv_context_put.
+
+   A nested call to __resolv_context_get (after another call to
+   __resolv_context_get without a matching __resolv_context_put call,
+   on the same thread) returns the original pointer, instead of
+   allocating a new context.  This prevents unexpected reloading of
+   the resolver configuration.  Care is taken to keep the context in
+   sync with the thread-local _res object.  (This does not happen with
+   __resolv_context_get_override, and __resolv_context_get_no_inet6 may
+   also interpose another context object if RES_USE_INET6 needs to be
+   disabled.)
+
+   In contrast to struct __res_state, struct resolv_context is not
+   affected by ABI compatibility concerns.
+
+   For the benefit of the res_n* functions, a struct __res_state
+   pointer is included in the context object, and a separate
+   initialization function is provided.  */
+
+#ifndef _RESOLV_CONTEXT_H
+#define _RESOLV_CONTEXT_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <bits/types/res_state.h>
+
+/* Temporary resolver state.  */
+struct resolv_context
+{
+  struct __res_state *resp;     /* Backing resolver state.   */
+
+
+  /* The following fields are for internal use within the
+     resolv_context module.  */
+  size_t __refcount;            /* Count of reusages by the get functions.  */
+  bool __from_res;              /* True if created from _res.  */
+
+  /* If RES_USE_INET6 was disabled at this level, this field points to
+     the previous context.  */
+  struct resolv_context *__next;
+};
+
+/* Return the current temporary resolver context, or NULL if there was
+   an error (indicated by errno).  A call to this function must be
+   paired with a call to __resolv_context_put.  */
+struct resolv_context *__resolv_context_get (void)
+  __attribute__ ((warn_unused_result));
+libc_hidden_proto (__resolv_context_get)
+
+/* Deallocate the temporary resolver context.  Converse of
+   __resolv_context_get.  Restore the RES_USE_INET6 flag if necessary.
+   Do nothing if CTX is NULL.  */
+void __resolv_context_put (struct resolv_context *ctx);
+libc_hidden_proto (__resolv_context_put)
+
+/* Like __resolv_context_get, but the _res structure can be partially
+   initialzed and those changes will not be overwritten.  */
+struct resolv_context *__resolv_context_get_preinit (void)
+  __attribute__ ((warn_unused_result));
+libc_hidden_proto (__resolv_context_get_preinit)
+
+/* Wrap a struct __res_state object in a struct resolv_context object.
+   A call to this function must be paired with a call to
+   __resolv_context_put.  */
+struct resolv_context *__resolv_context_get_override (struct __res_state *)
+  __attribute__ ((nonnull (1), warn_unused_result));
+libc_hidden_proto (__resolv_context_get_override)
+
+/* Called during thread shutdown to free the associated resolver
+   context (mostly in response to cancellation, otherwise the
+   __resolv_context_get/__resolv_context_put pairing will already have
+   deallocated the context object).  */
+void __resolv_context_freeres (void) attribute_hidden;
+
+#endif /* _RESOLV_CONTEXT_H */