diff mbox

libresolv tests

Message ID 45f7bb28-1a3a-ce4c-a8b9-7e9a4707d1fe@redhat.com
State New
Headers show

Commit Message

Florian Weimer Dec. 31, 2016, 1:15 p.m. UTC
The attached patch depends on the support/ enhancements I posted 
earlier.  It contains some of the short-running tests from my 
out-of-tree resolver test suite.

The included changes to support/ are fairly substantial, but I expect 
that other tests will use these facilities over time.

I plan to contribute more such libresolv tests, but some of them have to 
be xtests because they have many timeouts and really long run times.

Further work (RES_USE_INET6 deprecation and various libresolv cleanups 
and fixes) is blocked on the availability of the libresolv test 
framework within the glibc source tree.

Thanks,
Florian

Comments

Joseph Myers Dec. 31, 2016, 11:47 p.m. UTC | #1
I see build failures on alpha:

support_format_netent.c: In function 'support_format_netent':
support_format_netent.c:46:32: error: format '%x' expects argument of type 
'unsigned int', but argument 3 has type 'long unsigned int' 
[-Werror=format=]
   fprintf (mem.out, "net: 0x%08x\n", e->n_net);
                                ^

and on s390:

xwrite.c: In function 'xwrite':
xwrite.c:32:21: error: format '%zd' expects argument of type 'signed 
size_t', but argument 6 has type 'int' [-Werror=format=]
         FAIL_EXIT1 ("write of %zu bytes failed after %zd: %m",
                     ^
../support/check.h:40:53: note: in definition of macro 'FAIL_EXIT1'
   support_exit_failure_impl (1, __FILE__, __LINE__, __VA_ARGS__)
                                                     ^~~~~~~~~~~
xwrite.c:35:21: error: format '%zd' expects argument of type 'signed 
size_t', but argument 5 has type 'int' [-Werror=format=]
         FAIL_EXIT1 ("write return 0 after writing %zd bytes of %zu",
                     ^
../support/check.h:40:53: note: in definition of macro 'FAIL_EXIT1'
   support_exit_failure_impl (1, __FILE__, __LINE__, __VA_ARGS__)


https://sourceware.org/ml/libc-testresults/2016-q4/msg00096.html

-- 
Joseph S. Myers
joseph@codesourcery.com
Florian Weimer Jan. 1, 2017, 9:22 a.m. UTC | #2
On 01/01/2017 12:47 AM, Joseph Myers wrote:
> I see build failures on alpha:


> and on s390:


> https://sourceware.org/ml/libc-testresults/2016-q4/msg00096.html


Thanks, should be fixed.

Florian
diff mbox

Patch

resolv: Add beginnings of a libresolv test suite

2016-12-31  Florian Weimer  <fweimer@redhat.com>

	* resolv/Makefile (tests): Add tst-bug18665, tst-bug18665-tcp,
	tst-res_use_inet6, tst-resolv-basic, tst-resolv-network,
	tst-resolv-search.
	(tst-bug18665, tst-bug18665-tcp, tst-res_use_inet6)
	(tst-resolv-basic, tst-resolv-network, tst-resolv-search): Link
	with libresolv and libpthread.
	* resolv/tst-bug18665.c: New file.
	* resolv/tst-bug18665-tcp: Likewise.
	* resolv/tst-res_use_inet6: Likewise.
	* resolv/tst-resolv-basic: Likewise.
	* resolv/tst-resolv-network: Likewise.
	* resolv/tst-resolv-search: Likewise.
	* support/Makefile (libsupport-routines): Add check_addrinfo,
	check_dns_packet, check_hostent, check_netent, resolv_test,
	support_format_address_family, support_format_addrinfo,
	support_format_dns_packet, support_format_herrno,
	support_format_hostent, support_format_netent, support_run_diff,
	xaccept, xbind, xconnect, xfclose, xfopen, xgetsockname, xlisten,
	xmemstream, xpoll, xpthread_once, xrecvfrom, xsendto, xsetsockopt,
	xstrdup, xwrite.
	* support/check_addrinfo.c: New file.
	* support/check_dns_packet.c: Likewise.
	* support/check_hostent.c: Likewise.
	* support/check_netent.c: Likewise.
	* support/check_nss.h: Likewise.
	* support/format_nss.h: Likewise.
	* support/resolv_test.c: Likewise.
	* support/resolv_test.h: Likewise.
	* support/run_diff.h: Likewise.
	* support/support_format_address_family.c: Likewise.
	* support/support_format_addrinfo.c: Likewise.
	* support/support_format_dns_packet.c: Likewise.
	* support/support_format_herrno.c: Likewise.
	* support/support_format_hostent.c: Likewise.
	* support/support_format_netent.c: Likewise.
	* support/support_run_diff.c: Likewise.
	* support/xaccept.c: Likewise.
	* support/xbind.c: Likewise.
	* support/xconnect.c: Likewise.
	* support/xfclose.c: Likewise.
	* support/xfopen.c: Likewise.
	* support/xgetsockname.c: Likewise.
	* support/xlisten.c: Likewise.
	* support/xmemstream.c: Likewise.
	* support/xmemstream.h: Likewise.
	* support/xpoll.c: Likewise.
	* support/xpthread_once.c: Likewise.
	* support/xrecvfrom.c: Likewise.
	* support/xsendto.c: Likewise.
	* support/xsetsockopt.c: Likewise.
	* support/xstdio.h: Likewise.
	* support/xstrdup.c: Likewise.
	* support/support.h (xstrdup): Declare.
	* support/xsocket.h (xsetsockopt, xgetsockname, xconnect, xbind)
	(xlisten, xaccept, xsendto, xrecvfrom, xpoll): Likewise.
	* support/xthread.h (xpthread_once): Likwise.
	* support/xunistd.h (xwrite): Declare.

diff --git a/resolv/Makefile b/resolv/Makefile
index bd086e0..5eb10e3 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -39,7 +39,16 @@  extra-libs := libresolv libnss_dns
 ifeq ($(have-thread-library),yes)
 extra-libs += libanl
 routines += gai_sigqueue
-tests += tst-res_hconf_reorder
+
+tests += \
+  tst-bug18665 \
+  tst-bug18665-tcp \
+  tst-res_hconf_reorder \
+  tst-res_use_inet6 \
+  tst-resolv-basic \
+  tst-resolv-network \
+  tst-resolv-search \
+
 endif
 extra-libs-others = $(extra-libs)
 libresolv-routines := res_comp res_debug \
@@ -108,3 +117,10 @@  tst-leaks2-ENV = MALLOC_TRACE=$(objpfx)tst-leaks2.mtrace
 $(objpfx)mtrace-tst-leaks2.out: $(objpfx)tst-leaks2.out
 	$(common-objpfx)malloc/mtrace $(objpfx)tst-leaks2.mtrace > $@; \
 	$(evaluate-test)
+
+$(objpfx)tst-bug18665-tcp: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-bug18665: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-res_use_inet6: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-basic: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-network: $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
diff --git a/resolv/tst-bug18665-tcp.c b/resolv/tst-bug18665-tcp.c
new file mode 100644
index 0000000..fcd3baf
--- /dev/null
+++ b/resolv/tst-bug18665-tcp.c
@@ -0,0 +1,229 @@ 
+/* Test __libc_res_nsend buffer mismanagement, basic TCP coverage.
+   Copyright (C) 2016 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 <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/xthread.h>
+#include <support/xmemstream.h>
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int initial_address_count = 1;
+static int subsequent_address_count = 2000;
+static int response_number = 0;
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  TEST_VERIFY_EXIT (qname != NULL);
+
+  /* If not using TCP, just force its use.  */
+  if (!ctx->tcp)
+    {
+      struct resolv_response_flags flags = {.tc = true};
+      resolv_response_init (b, flags);
+      resolv_response_add_question (b, qname, qclass, qtype);
+      return;
+    }
+
+  struct resolv_response_flags flags = {};
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+
+  resolv_response_section (b, ns_s_an);
+
+  /* The number of addresses (in the additional section) for the name
+     server record (in the authoritative section).  */
+  int address_count;
+  xpthread_mutex_lock (&lock);
+  ++response_number;
+  if (response_number == 1)
+    address_count = initial_address_count;
+  else if (response_number == 2)
+    {
+      address_count = 0;
+      resolv_response_drop (b);
+      resolv_response_close (b);
+    }
+  else
+    address_count = subsequent_address_count;
+  xpthread_mutex_unlock (&lock);
+
+  /* Only add the address record to the answer section if we requested
+     any name server addresses.  */
+  if (address_count > 0)
+    {
+      resolv_response_open_record (b, qname, qclass, qtype, 0);
+      switch (qtype)
+        {
+        case T_A:
+          {
+            char ipv4[4] = {10, response_number >> 8, response_number, 0};
+            ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index;
+            resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+          }
+          break;
+        case T_AAAA:
+          {
+            char ipv6[16]
+              = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0,
+                 response_number >> 8, response_number, 0, 0};
+            ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index;
+            resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+          }
+          break;
+        default:
+          support_record_failure ();
+          printf ("error: unexpected QTYPE: %s/%u/%u\n",
+                  qname, qclass, qtype);
+        }
+      resolv_response_close_record (b);
+
+      /* Add the name server record.  */
+      resolv_response_section (b, ns_s_ns);
+      resolv_response_open_record (b, "example", C_IN, T_NS, 0);
+      resolv_response_add_name (b, "ns.example");
+      resolv_response_close_record (b);
+
+      /* Increase the response size with name server addresses.  These
+         addresses are not copied out of nss_dns, and thus do not
+         trigger getaddrinfo retries with a larger buffer, making
+         testing more predictable.  */
+      resolv_response_section (b, ns_s_ar);
+      for (int i = 1; i <= address_count; ++i)
+        {
+          resolv_response_open_record (b, "ns.example", qclass, qtype, 0);
+          switch (qtype)
+            {
+            case T_A:
+              {
+                char ipv4[4] = {response_number, i >> 8, i, 0};
+                ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index;
+                resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+              }
+              break;
+            case T_AAAA:
+              {
+                char ipv6[16]
+                  = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0,
+                     response_number >> 8, response_number,
+                     i >> 8, i, 0, 0};
+                ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index;
+                resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+              }
+              break;
+            default:
+              support_record_failure ();
+              printf ("error: unexpected QTYPE: %s/%u/%u\n",
+                      qname, qclass, qtype);
+            }
+          resolv_response_close_record (b);
+        }
+    }
+}
+
+static char *
+expected_result (unsigned port, unsigned response_number)
+{
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+  /* We fail the second TCP query to the first server by closing the
+     connection immediately, without returning any data.  This should
+     cause failover to the second server.  */
+  int server_index = 1;
+  fprintf (mem.out, "address: STREAM/TCP 10.%u.%u.%u %u\n",
+           (response_number >> 8) & 0xff, response_number & 0xff,
+           2 + 4 * server_index, port);
+  fprintf (mem.out, "address: STREAM/TCP 2001:db8::%x:%x %u\n",
+           (response_number + 1) & 0xffff,
+           2 + 4 * server_index, port);
+  xfclose_memstream (&mem);
+  return mem.buffer;
+}
+
+static void
+test_different_sizes (void)
+{
+  struct addrinfo hints =
+    {
+      .ai_family = AF_UNSPEC,
+      .ai_socktype = SOCK_STREAM,
+      .ai_protocol = IPPROTO_TCP,
+    };
+  struct addrinfo *ai;
+  char *expected;
+  int ret;
+
+  /* This magic number produces a response size close to 2048
+     bytes.  */
+  initial_address_count = 124;
+  response_number = 0;
+
+  ret = getaddrinfo ("www.example", "80", &hints, &ai);
+  expected = expected_result (80, 3);
+  check_addrinfo ("www.example:80", ai, ret, expected);
+  if (ret == 0)
+    freeaddrinfo (ai);
+  free (expected);
+
+  response_number = 0;
+  ret = getaddrinfo ("www123.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+
+  response_number = 0;
+  ret = getaddrinfo ("www1234.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+
+  response_number = 0;
+  ret = getaddrinfo ("www12345.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *obj = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response
+     });
+
+  test_different_sizes ();
+
+  _res.options |= RES_SNGLKUP;
+  test_different_sizes ();
+
+  _res.options |= RES_SNGLKUPREOP;
+  test_different_sizes ();
+
+  resolv_test_end (obj);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-bug18665.c b/resolv/tst-bug18665.c
new file mode 100644
index 0000000..2d0cdb2
--- /dev/null
+++ b/resolv/tst-bug18665.c
@@ -0,0 +1,138 @@ 
+/* Test for __libc_res_nsend buffer mismanagent (bug 18665), UDP case.
+   Copyright (C) 2016 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 <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdio.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/resolv_test.h>
+#include <support/xthread.h>
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+
+static int initial_address_count;
+static int response_count;
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  TEST_VERIFY_EXIT (qname != NULL);
+  struct resolv_response_flags flags = {};
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+
+  resolv_response_section (b, ns_s_an);
+
+  /* Add many A/AAAA records to the second response.  */
+  int address_count;
+  xpthread_mutex_lock (&lock);
+  if (response_count == 0)
+    address_count = initial_address_count;
+  else
+    address_count = 2000;
+  ++response_count;
+  xpthread_mutex_unlock (&lock);
+
+  for (int i = 0; i < address_count; ++i)
+    {
+      resolv_response_open_record (b, qname, qclass, qtype, 0);
+      switch (qtype)
+        {
+        case T_A:
+          {
+            char ipv4[4] = {10, i >> 8, i, 0};
+            ipv4[3] = 2 * ctx->tcp + 4 * ctx->server_index;
+            resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+          }
+          break;
+        case T_AAAA:
+          {
+            char ipv6[16]
+              = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                 i >> 8, i, 0};
+            ipv6[15] = 2 * ctx->tcp + 4 * ctx->server_index;
+            resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+          }
+          break;
+        default:
+          support_record_failure ();
+          printf ("error: unexpected QTYPE: %s/%u/%u\n",
+                  qname, qclass, qtype);
+        }
+      resolv_response_close_record (b);
+    }
+}
+
+static void
+test_different_sizes (void)
+{
+  struct addrinfo hints = { .ai_family = AF_UNSPEC, };
+  struct addrinfo *ai;
+  int ret;
+
+  /* This magic number produces a response size close to 2048
+     bytes.  */
+  initial_address_count = 126;
+  response_count = 0;
+
+  ret = getaddrinfo ("www.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+
+  response_count = 0;
+  ret = getaddrinfo ("www123.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+
+  response_count = 0;
+  ret = getaddrinfo ("www1234.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+
+  response_count = 0;
+  ret = getaddrinfo ("www12345.example", "80", &hints, &ai);
+  if (ret == 0)
+    freeaddrinfo (ai);
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *obj = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response
+     });
+
+  test_different_sizes ();
+
+  _res.options |= RES_SNGLKUP;
+  test_different_sizes ();
+
+  _res.options |= RES_SNGLKUPREOP;
+  test_different_sizes ();
+
+  resolv_test_end (obj);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-res_use_inet6.c b/resolv/tst-res_use_inet6.c
new file mode 100644
index 0000000..5ed0ad0
--- /dev/null
+++ b/resolv/tst-res_use_inet6.c
@@ -0,0 +1,201 @@ 
+/* Basic functionality tests for inet6 option processing.
+   Copyright (C) 2016 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 <netdb.h>
+#include <resolv.h>
+#include <string.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/xthread.h>
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  bool include_both =  strcmp (qname, "both.example") == 0;
+  bool include_a = qtype == T_A || include_both;
+  bool include_aaaa = qtype == T_AAAA || include_both;
+
+  resolv_response_init (b, (struct resolv_response_flags) {});
+  resolv_response_add_question (b, qname, qclass, qtype);
+  resolv_response_section (b, ns_s_an);
+  if (include_a)
+    {
+      char ipv4[4] = {192, 0, 2, 17};
+      resolv_response_open_record (b, qname, qclass, T_A, 0);
+      resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+      resolv_response_close_record (b);
+    }
+  if (include_aaaa)
+    {
+        char ipv6[16]
+          = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
+        resolv_response_open_record (b, qname, qclass, T_AAAA, 0);
+        resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+        resolv_response_close_record (b);
+    }
+}
+
+/* Test that getaddrinfo is not influenced by RES_USE_INET6.  */
+static void
+test_gai (void)
+{
+  {
+    struct addrinfo hints =
+      {
+        .ai_family = AF_UNSPEC,
+        .ai_socktype = SOCK_STREAM,
+        .ai_protocol = IPPROTO_TCP,
+      };
+    struct addrinfo *ai;
+    int ret = getaddrinfo ("www1.example", "80", &hints, &ai);
+    check_addrinfo ("getaddrinfo AF_UNSPEC www1.example", ai, ret,
+                    "address: STREAM/TCP 192.0.2.17 80\n"
+                    "address: STREAM/TCP 2001:db8::1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+    ret = getaddrinfo ("both.example", "80", &hints, &ai);
+    /* Combined A/AAAA responses currently result in address
+       duplication.  */
+    check_addrinfo ("getaddrinfo AF_UNSPEC both.example", ai, ret,
+                    "address: STREAM/TCP 192.0.2.17 80\n"
+                    "address: STREAM/TCP 192.0.2.17 80\n"
+                    "address: STREAM/TCP 2001:db8::1 80\n"
+                    "address: STREAM/TCP 2001:db8::1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+  }
+  {
+    struct addrinfo hints =
+      {
+        .ai_family = AF_INET,
+        .ai_socktype = SOCK_STREAM,
+        .ai_protocol = IPPROTO_TCP,
+      };
+    struct addrinfo *ai;
+    int ret = getaddrinfo ("www1.example", "80", &hints, &ai);
+    check_addrinfo ("getaddrinfo AF_INET www1.example", ai, ret,
+                    "address: STREAM/TCP 192.0.2.17 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+    ret = getaddrinfo ("both.example", "80", &hints, &ai);
+    check_addrinfo ("getaddrinfo AF_INET both.example", ai, ret,
+                    "address: STREAM/TCP 192.0.2.17 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+  }
+  {
+    struct addrinfo hints =
+      {
+        .ai_family = AF_INET6,
+        .ai_socktype = SOCK_STREAM,
+        .ai_protocol = IPPROTO_TCP,
+      };
+    struct addrinfo *ai;
+    int ret = getaddrinfo ("www1.example", "80", &hints, &ai);
+    check_addrinfo ("getaddrinfo (AF_INET6)", ai, ret,
+                    "address: STREAM/TCP 2001:db8::1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+    ret = getaddrinfo ("both.example", "80", &hints, &ai);
+    check_addrinfo ("getaddrinfo AF_INET6 both.example", ai, ret,
+                    "address: STREAM/TCP 2001:db8::1 80\n");
+    if (ret == 0)
+      freeaddrinfo (ai);
+  }
+}
+
+/* Test that gethostbyname2 is not influenced by RES_USE_INET6.  */
+static void
+test_get2 (void)
+{
+  check_hostent ("gethostbyname2 AF_INET www1.example",
+                 gethostbyname2 ("www1.example", AF_INET),
+                 "name: www1.example\n"
+                 "address: 192.0.2.17\n");
+  check_hostent ("gethostbyname2 AF_INET both.example",
+                 gethostbyname2 ("both.example", AF_INET),
+                 "name: both.example\n"
+                 "address: 192.0.2.17\n");
+
+  check_hostent ("gethostbyname2 AF_INET6 www1.example",
+                 gethostbyname2 ("www1.example", AF_INET6),
+                 "name: www1.example\n"
+                 "address: 2001:db8::1\n");
+  check_hostent ("gethostbyname2 AF_INET6 both.example",
+                 gethostbyname2 ("both.example", AF_INET6),
+                 "name: both.example\n"
+                 "address: 2001:db8::1\n");
+}
+
+static void *
+threadfunc (void *ignored)
+{
+  struct resolv_test *obj = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response
+     });
+
+  check_hostent ("gethostbyname (\"www1.example\")",
+                 gethostbyname ("www1.example"),
+                 "name: www1.example\n"
+                 "address: 192.0.2.17\n");
+  check_hostent ("gethostbyname (\"both.example\")",
+                 gethostbyname ("both.example"),
+                 "name: both.example\n"
+                 "address: 192.0.2.17\n");
+  test_get2 ();
+  test_gai ();
+
+  _res.options |= RES_USE_INET6;
+  check_hostent ("gethostbyname (\"www1.example\")",
+                 gethostbyname ("www1.example"),
+                 "name: www1.example\n"
+                 "address: 2001:db8::1\n");
+  check_hostent ("gethostbyname (\"both.example\")",
+                 gethostbyname ("both.example"),
+                 "name: both.example\n"
+                 "address: 2001:db8::1\n");
+  test_get2 ();
+  test_gai ();
+
+  resolv_test_end (obj);
+
+  return NULL;
+}
+
+static int
+do_test (void)
+{
+  resolv_test_init ();
+
+  /* Attempt to run on a non-main thread first.  */
+  {
+    pthread_t thr = xpthread_create (NULL, threadfunc, NULL);
+    xpthread_join (thr);
+  }
+
+  /* Try the main thread next.  */
+  threadfunc (NULL);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-basic.c b/resolv/tst-resolv-basic.c
new file mode 100644
index 0000000..78c581d
--- /dev/null
+++ b/resolv/tst-resolv-basic.c
@@ -0,0 +1,315 @@ 
+/* Test basic nss_dns functionality and the resolver test harness itself.
+   Copyright (C) 2016 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  TEST_VERIFY_EXIT (qname != NULL);
+
+  /* The "t." prefix can be used to request TCP fallback.  */
+  bool force_tcp;
+  if (strncmp ("t.", qname, 2) == 0)
+    force_tcp = true;
+  else
+    force_tcp = false;
+  const char *qname_compare;
+  if (force_tcp)
+    qname_compare = qname + 2;
+  else
+    qname_compare = qname;
+  enum {www, alias, nxdomain} requested_qname;
+  if (strcmp (qname_compare, "www.example") == 0)
+    requested_qname = www;
+  else if (strcmp (qname_compare, "alias.example") == 0)
+    requested_qname = alias;
+  else if (strcmp (qname_compare, "nxdomain.example") == 0)
+    requested_qname = nxdomain;
+  else
+    {
+      support_record_failure ();
+      printf ("error: unexpected QNAME: %s\n", qname);
+      return;
+    }
+  TEST_VERIFY_EXIT (qclass == C_IN);
+  struct resolv_response_flags flags = {.tc = force_tcp && !ctx->tcp};
+  if (requested_qname == nxdomain)
+    flags.rcode = 3;            /* NXDOMAIN */
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+  if (requested_qname == nxdomain || flags.tc)
+    return;
+
+  resolv_response_section (b, ns_s_an);
+  switch (requested_qname)
+    {
+    case www:
+      resolv_response_open_record (b, qname, qclass, qtype, 0);
+      break;
+    case alias:
+      resolv_response_open_record (b, qname, qclass, T_CNAME, 0);
+      resolv_response_add_name (b, "www.example");
+      resolv_response_close_record (b);
+      resolv_response_open_record (b, "www.example", qclass, qtype, 0);
+      break;
+    case nxdomain:
+      FAIL_EXIT1 ("unreachable");
+    }
+  switch (qtype)
+    {
+    case T_A:
+      {
+        char ipv4[4] = {192, 0, 2, 17};
+        ipv4[3] += requested_qname + 2 * ctx->tcp + 4 * ctx->server_index;
+        resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+      }
+      break;
+    case T_AAAA:
+      {
+        char ipv6[16]
+          = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
+        ipv6[15] += requested_qname + 2 * ctx->tcp + 4 * ctx->server_index;
+        resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+      }
+      break;
+    default:
+      support_record_failure ();
+      printf ("error: unexpected QTYPE: %s/%u/%u\n",
+              qname, qclass, qtype);
+    }
+  resolv_response_close_record (b);
+}
+
+static void
+check_h (const char *name, int family, const char *expected)
+{
+  if (family == AF_INET)
+    {
+      char *query = xasprintf ("gethostbyname (\"%s\")", name);
+      check_hostent (query, gethostbyname (name), expected);
+      free (query);
+    }
+  {
+    char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family);
+    check_hostent (query, gethostbyname2 (name, family), expected);
+    free (query);
+  }
+
+  bool too_small = true;
+  for (unsigned int offset = 0; offset < 8; ++offset)
+    for (unsigned int size = 1; too_small; ++size)
+      {
+        char *buf = xmalloc (offset + size);
+        too_small = false;
+
+        struct hostent hostbuf;
+        struct hostent *result;
+        int herror;
+        if (family == AF_INET)
+          {
+            char *query = xasprintf ("gethostbyname (\"%s\") %u/%u",
+                                     name, offset, size);
+            int ret = gethostbyname_r
+              (name, &hostbuf, buf + offset, size, &result, &herror);
+            if (ret == 0)
+              {
+                h_errno = herror;
+                check_hostent (query, result, expected);
+              }
+            else if (ret == ERANGE)
+              too_small = true;
+            else
+              {
+                errno = ret;
+                FAIL_EXIT1 ("gethostbyname_r: %m");
+              }
+            free (query);
+            memset (buf, 0, offset + size);
+          }
+        char *query = xasprintf ("gethostbyname2 (\"%s\", %d) %u/%u",
+                                 name, family, offset, size);
+        int ret = gethostbyname2_r
+          (name, family, &hostbuf, buf + offset, size, &result, &herror);
+        if (ret == 0)
+          {
+            h_errno = herror;
+            check_hostent (query, result, expected);
+          }
+        else if (ret == ERANGE)
+          too_small = true;
+        else
+          {
+            errno = ret;
+            FAIL_EXIT1 ("gethostbyname_r: %m");
+          }
+        free (buf);
+        free (query);
+      }
+}
+
+static void
+check_ai (const char *name, const char *service,
+          int family, const char *expected)
+{
+  struct addrinfo hints = {.ai_family = family};
+  struct addrinfo *ai;
+  char *query = xasprintf ("%s:%s [%d]", name, service, family);
+  int ret = getaddrinfo (name, service, &hints, &ai);
+  check_addrinfo (query, ai, ret, expected);
+  if (ret == 0)
+    freeaddrinfo (ai);
+  free (query);
+}
+
+static int
+do_test (void)
+{
+  struct resolv_test *aux = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response,
+     });
+
+  check_h ("www.example", AF_INET,
+           "name: www.example\n"
+           "address: 192.0.2.17\n");
+  check_h ("alias.example", AF_INET,
+           "name: www.example\n"
+           "alias: alias.example\n"
+           "address: 192.0.2.18\n");
+  check_h ("www.example", AF_INET6,
+           "name: www.example\n"
+           "address: 2001:db8::1\n");
+  check_h ("alias.example", AF_INET6,
+           "name: www.example\n"
+           "alias: alias.example\n"
+           "address: 2001:db8::2\n");
+  check_ai ("www.example", "80", AF_UNSPEC,
+            "address: STREAM/TCP 192.0.2.17 80\n"
+            "address: DGRAM/UDP 192.0.2.17 80\n"
+            "address: RAW/IP 192.0.2.17 80\n"
+            "address: STREAM/TCP 2001:db8::1 80\n"
+            "address: DGRAM/UDP 2001:db8::1 80\n"
+            "address: RAW/IP 2001:db8::1 80\n");
+  check_ai ("alias.example", "80", AF_UNSPEC,
+            "address: STREAM/TCP 192.0.2.18 80\n"
+            "address: DGRAM/UDP 192.0.2.18 80\n"
+            "address: RAW/IP 192.0.2.18 80\n"
+            "address: STREAM/TCP 2001:db8::2 80\n"
+            "address: DGRAM/UDP 2001:db8::2 80\n"
+            "address: RAW/IP 2001:db8::2 80\n");
+  check_ai ("www.example", "80", AF_INET,
+            "address: STREAM/TCP 192.0.2.17 80\n"
+            "address: DGRAM/UDP 192.0.2.17 80\n"
+            "address: RAW/IP 192.0.2.17 80\n");
+  check_ai ("alias.example", "80", AF_INET,
+            "address: STREAM/TCP 192.0.2.18 80\n"
+            "address: DGRAM/UDP 192.0.2.18 80\n"
+            "address: RAW/IP 192.0.2.18 80\n");
+  check_ai ("www.example", "80", AF_INET6,
+            "address: STREAM/TCP 2001:db8::1 80\n"
+            "address: DGRAM/UDP 2001:db8::1 80\n"
+            "address: RAW/IP 2001:db8::1 80\n");
+  check_ai ("alias.example", "80", AF_INET6,
+            "address: STREAM/TCP 2001:db8::2 80\n"
+            "address: DGRAM/UDP 2001:db8::2 80\n"
+            "address: RAW/IP 2001:db8::2 80\n");
+
+  check_h ("t.www.example", AF_INET,
+           "name: t.www.example\n"
+           "address: 192.0.2.19\n");
+  check_h ("t.alias.example", AF_INET,
+           "name: www.example\n"
+           "alias: t.alias.example\n"
+           "address: 192.0.2.20\n");
+  check_h ("t.www.example", AF_INET6,
+           "name: t.www.example\n"
+           "address: 2001:db8::3\n");
+  check_h ("t.alias.example", AF_INET6,
+           "name: www.example\n"
+           "alias: t.alias.example\n"
+           "address: 2001:db8::4\n");
+  check_ai ("t.www.example", "80", AF_UNSPEC,
+            "address: STREAM/TCP 192.0.2.19 80\n"
+            "address: DGRAM/UDP 192.0.2.19 80\n"
+            "address: RAW/IP 192.0.2.19 80\n"
+            "address: STREAM/TCP 2001:db8::3 80\n"
+            "address: DGRAM/UDP 2001:db8::3 80\n"
+            "address: RAW/IP 2001:db8::3 80\n");
+  check_ai ("t.alias.example", "80", AF_UNSPEC,
+            "address: STREAM/TCP 192.0.2.20 80\n"
+            "address: DGRAM/UDP 192.0.2.20 80\n"
+            "address: RAW/IP 192.0.2.20 80\n"
+            "address: STREAM/TCP 2001:db8::4 80\n"
+            "address: DGRAM/UDP 2001:db8::4 80\n"
+            "address: RAW/IP 2001:db8::4 80\n");
+  check_ai ("t.www.example", "80", AF_INET,
+            "address: STREAM/TCP 192.0.2.19 80\n"
+            "address: DGRAM/UDP 192.0.2.19 80\n"
+            "address: RAW/IP 192.0.2.19 80\n");
+  check_ai ("t.alias.example", "80", AF_INET,
+            "address: STREAM/TCP 192.0.2.20 80\n"
+            "address: DGRAM/UDP 192.0.2.20 80\n"
+            "address: RAW/IP 192.0.2.20 80\n");
+  check_ai ("t.www.example", "80", AF_INET6,
+            "address: STREAM/TCP 2001:db8::3 80\n"
+            "address: DGRAM/UDP 2001:db8::3 80\n"
+            "address: RAW/IP 2001:db8::3 80\n");
+  check_ai ("t.alias.example", "80", AF_INET6,
+            "address: STREAM/TCP 2001:db8::4 80\n"
+            "address: DGRAM/UDP 2001:db8::4 80\n"
+            "address: RAW/IP 2001:db8::4 80\n");
+
+  check_h ("nxdomain.example", AF_INET,
+           "error: HOST_NOT_FOUND\n");
+  check_h ("nxdomain.example", AF_INET6,
+           "error: HOST_NOT_FOUND\n");
+  check_ai ("nxdomain.example", "80", AF_UNSPEC,
+            "error: Name or service not known\n");
+  check_ai ("nxdomain.example", "80", AF_INET,
+            "error: Name or service not known\n");
+  check_ai ("nxdomain.example", "80", AF_INET6,
+            "error: Name or service not known\n");
+
+  check_h ("t.nxdomain.example", AF_INET,
+           "error: HOST_NOT_FOUND\n");
+  check_h ("t.nxdomain.example", AF_INET6,
+           "error: HOST_NOT_FOUND\n");
+  check_ai ("t.nxdomain.example", "80", AF_UNSPEC,
+            "error: Name or service not known\n");
+  check_ai ("t.nxdomain.example", "80", AF_INET,
+            "error: Name or service not known\n");
+  check_ai ("t.nxdomain.example", "80", AF_INET6,
+            "error: Name or service not known\n");
+
+  resolv_test_end (aux);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-network.c b/resolv/tst-resolv-network.c
new file mode 100644
index 0000000..f248527
--- /dev/null
+++ b/resolv/tst-resolv-network.c
@@ -0,0 +1,299 @@ 
+/* Test getnetbyname and getnetbyaddr.
+   Copyright (C) 2016 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 <netdb.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+static void
+send_ptr (struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype,
+          const char *alias)
+{
+  resolv_response_init (b, (struct resolv_response_flags) {});
+  resolv_response_add_question (b, qname, qclass, qtype);
+  resolv_response_section (b, ns_s_an);
+  resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+  resolv_response_add_name (b, alias);
+  resolv_response_close_record (b);
+}
+
+static void
+handle_code (const struct resolv_response_context *ctx,
+             struct resolv_response_builder *b,
+             const char *qname, uint16_t qclass, uint16_t qtype,
+             int code)
+{
+  switch (code)
+    {
+    case 1:
+      send_ptr (b, qname, qclass, qtype, "1.in-addr.arpa");
+      break;
+    case 2:
+      send_ptr (b, qname, qclass, qtype, "2.1.in-addr.arpa");
+      break;
+    case 3:
+      send_ptr (b, qname, qclass, qtype, "3.2.1.in-addr.arpa");
+      break;
+    case 4:
+      send_ptr (b, qname, qclass, qtype, "4.3.2.1.in-addr.arpa");
+      break;
+    case 5:
+      /* Test multiple PTR records.  */
+      resolv_response_init (b, (struct resolv_response_flags) {});
+      resolv_response_add_question (b, qname, qclass, qtype);
+      resolv_response_section (b, ns_s_an);
+      resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+      resolv_response_add_name (b, "127.in-addr.arpa");
+      resolv_response_close_record (b);
+      resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+      resolv_response_add_name (b, "0.in-addr.arpa");
+      resolv_response_close_record (b);
+      break;
+    case 6:
+      /* Test skipping of RRSIG record.  */
+      resolv_response_init (b, (struct resolv_response_flags) { });
+      resolv_response_add_question (b, qname, qclass, qtype);
+      resolv_response_section (b, ns_s_an);
+
+      resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+      resolv_response_add_name (b, "127.in-addr.arpa");
+      resolv_response_close_record (b);
+
+      resolv_response_open_record (b, qname, qclass, 46 /* RRSIG */, 0);
+      {
+        char buf[500];
+        memset (buf, 0x3f, sizeof (buf));
+        resolv_response_add_data (b, buf, sizeof (buf));
+      }
+      resolv_response_close_record (b);
+
+      resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+      resolv_response_add_name (b, "0.in-addr.arpa");
+      resolv_response_close_record (b);
+      break;
+    case 7:
+      /* Test CNAME handling.  */
+      resolv_response_init (b, (struct resolv_response_flags) { });
+      resolv_response_add_question (b, qname, qclass, qtype);
+      resolv_response_section (b, ns_s_an);
+      resolv_response_open_record (b, qname, qclass, T_CNAME, 0);
+      resolv_response_add_name (b, "cname.example");
+      resolv_response_close_record (b);
+      resolv_response_open_record (b, "cname.example", qclass, T_PTR, 0);
+      resolv_response_add_name (b, "4.3.2.1.in-addr.arpa");
+      resolv_response_close_record (b);
+      break;
+
+    case 100:
+      resolv_response_init (b, (struct resolv_response_flags) { .rcode = 0, });
+      resolv_response_add_question (b, qname, qclass, qtype);
+      break;
+    case 101:
+      resolv_response_init (b, (struct resolv_response_flags)
+                            { .rcode = NXDOMAIN, });
+      resolv_response_add_question (b, qname, qclass, qtype);
+      break;
+    case 102:
+      resolv_response_init (b, (struct resolv_response_flags) {.rcode = SERVFAIL});
+      resolv_response_add_question (b, qname, qclass, qtype);
+      break;
+    case 103:
+      /* Check response length matching.  */
+      if (!ctx->tcp)
+        {
+          resolv_response_init (b, (struct resolv_response_flags) {.tc = true});
+          resolv_response_add_question (b, qname, qclass, qtype);
+        }
+      else
+        {
+          resolv_response_init (b, (struct resolv_response_flags) {.ancount = 1});
+          resolv_response_add_question (b, qname, qclass, qtype);
+          resolv_response_section (b, ns_s_an);
+          resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+          resolv_response_add_name (b, "127.in-addr.arpa");
+          resolv_response_close_record (b);
+          resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+          resolv_response_add_name (b, "example");
+          resolv_response_close_record (b);
+
+          resolv_response_open_record (b, qname, qclass, T_PTR, 0);
+          size_t to_fill = 65535 - resolv_response_length (b)
+            - 2 /* length, "n" */ - 2 /* compression reference */
+            - 2 /* RR type */;
+          for (size_t i = 0; i < to_fill; ++i)
+            resolv_response_add_data (b, "", 1);
+          resolv_response_close_record (b);
+          resolv_response_add_name (b, "n.example");
+          uint16_t rrtype = htons (T_PTR);
+          resolv_response_add_data (b, &rrtype, sizeof (rrtype));
+        }
+      break;
+    default:
+      FAIL_EXIT1 ("invalid QNAME: %s (code %d)", qname, code);
+    }
+}
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  int code;
+  if (strstr (qname, "in-addr.arpa") == NULL)
+    {
+      char *tail;
+      if (sscanf (qname, "code%d.%ms", &code, &tail) != 2
+          || strcmp (tail, "example") != 0)
+        FAIL_EXIT1 ("invalid QNAME: %s", qname);
+      free (tail);
+      handle_code (ctx, b, qname, qclass, qtype, code);
+    }
+  else
+    {
+      /* Reverse lookup.  */
+      int components[4];
+      char *tail;
+      if (sscanf (qname, "%d.%d.%d.%d.%ms",
+                  components, components + 1, components + 2, components + 3,
+                  &tail) != 5
+          || strcmp (tail, "in-addr.arpa") != 0)
+        FAIL_EXIT1 ("invalid QNAME: %s", qname);
+      free (tail);
+      handle_code (ctx, b, qname, qclass, qtype, components[3]);
+    }
+}
+
+static void
+check_reverse (int code, const char *expected)
+{
+  char *query = xasprintf ("code=%d", code);
+  check_netent (query, getnetbyaddr (code, AF_INET), expected);
+  free (query);
+}
+
+/* Test for CVE-2016-3075.  */
+static void
+check_long_name (void)
+{
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+
+  char label[65];
+  memset (label, 'x', 63);
+  label[63] = '.';
+  label[64] = '\0';
+  for (unsigned i = 0; i < 64 * 1024 * 1024 / strlen (label); ++i)
+    fprintf (mem.out, "%s", label);
+
+  xfclose_memstream (&mem);
+
+  check_netent ("long name", getnetbyname (mem.buffer),
+                "error: NO_RECOVERY\n");
+
+  free (mem.buffer);
+}
+
+int
+main (void)
+{
+  struct resolv_test *obj = resolv_test_start
+    ((struct resolv_redirect_config)
+     {
+       .response_callback = response
+     });
+
+  /* Lookup by name, success cases.  */
+  check_netent ("code1.example", getnetbyname ("code1.example"),
+                "alias: 1.in-addr.arpa\n"
+                "net: 0x00000001\n");
+  check_netent ("code2.example", getnetbyname ("code2.example"),
+                "alias: 2.1.in-addr.arpa\n"
+                "net: 0x00000102\n");
+  check_netent ("code3.example", getnetbyname ("code3.example"),
+                "alias: 3.2.1.in-addr.arpa\n"
+                "net: 0x00010203\n");
+  check_netent ("code4.example", getnetbyname ("code4.example"),
+                "alias: 4.3.2.1.in-addr.arpa\n"
+                "net: 0x01020304\n");
+  check_netent ("code5.example", getnetbyname ("code5.example"),
+                "alias: 127.in-addr.arpa\n"
+                "alias: 0.in-addr.arpa\n"
+                "net: 0x0000007f\n");
+  check_netent ("code6.example", getnetbyname ("code6.example"),
+                "alias: 127.in-addr.arpa\n"
+                "alias: 0.in-addr.arpa\n"
+                "net: 0x0000007f\n");
+  check_netent ("code7.example", getnetbyname ("code7.example"),
+                "alias: 4.3.2.1.in-addr.arpa\n"
+                "net: 0x01020304\n");
+
+  /* Lookup by name, failure cases.  */
+  check_netent ("code100.example", getnetbyname ("code100.example"),
+                "error: NO_ADDRESS\n");
+  check_netent ("code101.example", getnetbyname ("code101.example"),
+                "error: HOST_NOT_FOUND\n");
+  check_netent ("code102.example", getnetbyname ("code102.example"),
+                "error: TRY_AGAIN\n");
+  check_netent ("code103.example", getnetbyname ("code103.example"),
+                "error: NO_RECOVERY\n");
+
+  /* Lookup by address, success cases.  */
+  check_reverse (1,
+                 "name: 1.in-addr.arpa\n"
+                 "net: 0x00000001\n");
+  check_reverse (2,
+                 "name: 2.1.in-addr.arpa\n"
+                 "net: 0x00000002\n");
+  check_reverse (3,
+                 "name: 3.2.1.in-addr.arpa\n"
+                 "net: 0x00000003\n");
+  check_reverse (4,
+                 "name: 4.3.2.1.in-addr.arpa\n"
+                 "net: 0x00000004\n");
+  check_reverse (5,
+                 "name: 127.in-addr.arpa\n"
+                 "alias: 0.in-addr.arpa\n"
+                 "net: 0x00000005\n");
+  check_reverse (6,
+                 "name: 127.in-addr.arpa\n"
+                 "alias: 0.in-addr.arpa\n"
+                 "net: 0x00000006\n");
+  check_reverse (7,
+                 "name: 4.3.2.1.in-addr.arpa\n"
+                 "net: 0x00000007\n");
+
+  /* Lookup by address, failure cases.  */
+  check_reverse (100,
+                 "error: NO_ADDRESS\n");
+  check_reverse (101,
+                 "error: HOST_NOT_FOUND\n");
+  check_reverse (102,
+                 "error: TRY_AGAIN\n");
+  check_reverse (103,
+                 "error: NO_RECOVERY\n");
+
+  check_long_name ();
+
+  resolv_test_end (obj);
+}
diff --git a/resolv/tst-resolv-search.c b/resolv/tst-resolv-search.c
new file mode 100644
index 0000000..d95f9d9
--- /dev/null
+++ b/resolv/tst-resolv-search.c
@@ -0,0 +1,343 @@ 
+/* Test search/default domain name behavior.
+   Copyright (C) 2016 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.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+struct item
+{
+  const char *name;
+  int response;
+};
+
+const struct item items[] =
+  {
+    {"hostname.usersys.example.com", 1},
+    {"hostname.corp.example.com", 1},
+    {"hostname.example.com", 1},
+
+    {"mail.corp.example.com", 1},
+    {"mail.example.com", 1},
+
+    {"file.corp.example.com", 2},
+    {"file.corp", 1},
+    {"file.example.com", 1},
+    {"servfail-usersys.usersys.example.com", -ns_r_servfail},
+    {"servfail-usersys.corp.example.com", 1},
+    {"servfail-usersys.example.com", 1},
+    {"servfail-corp.usersys.example.com", 1},
+    {"servfail-corp.corp.example.com", -ns_r_servfail},
+    {"servfail-corp.example.com", 1},
+    {"www.example.com", 1},
+    {"large.example.com", 200},
+
+    /* Test query amplification with a SERVFAIL response combined with
+       a large RRset.  */
+    {"large-servfail.usersys.example.com", -ns_r_servfail},
+    {"large-servfail.example.com", 2000},
+    {}
+  };
+
+enum
+  {
+    name_not_found = -1,
+    name_no_data = -2
+  };
+
+static int
+find_name (const char *name)
+{
+  for (int i = 0; items[i].name != NULL; ++i)
+    {
+      if (strcmp (name, items[i].name) == 0)
+        return i;
+    }
+  if (strcmp (name, "example.com") == 0
+      || strcmp (name, "usersys.example.com") == 0
+      || strcmp (name, "corp.example.com") == 0)
+    return name_no_data;
+  return name_not_found;
+}
+
+static int rcode_override_server_index = -1;
+static int rcode_override;
+
+static void
+response (const struct resolv_response_context *ctx,
+          struct resolv_response_builder *b,
+          const char *qname, uint16_t qclass, uint16_t qtype)
+{
+  if (ctx->server_index == rcode_override_server_index)
+    {
+      struct resolv_response_flags flags = {.rcode = rcode_override};
+      resolv_response_init (b, flags);
+      resolv_response_add_question (b, qname, qclass, qtype);
+      return;
+    }
+
+  int index = find_name (qname);
+  struct resolv_response_flags flags = {};
+  if (index == name_not_found)
+    flags.rcode = ns_r_nxdomain;
+  else if (index >= 0 && items[index].response < 0)
+    flags.rcode = -items[index].response;
+  else if (index >= 0 && items[index].response > 5 && !ctx->tcp)
+    /* Force TCP if more than 5 addresses where requested.  */
+    flags.tc = true;
+  resolv_response_init (b, flags);
+  resolv_response_add_question (b, qname, qclass, qtype);
+
+  if (flags.tc || index < 0 || items[index].response < 0)
+    return;
+
+  resolv_response_section (b, ns_s_an);
+
+  for (int i = 0; i < items[index].response; ++i)
+    {
+      resolv_response_open_record (b, qname, qclass, qtype, 0);
+
+      switch (qtype)
+        {
+        case T_A:
+          {
+            char addr[4] = {10, index, i >> 8, i};
+            resolv_response_add_data (b, addr, sizeof (addr));
+          }
+          break;
+        case T_AAAA:
+          {
+            char addr[16]
+              = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0,
+                 0, index + 1, (i + 1) >> 8, i + 1};
+            resolv_response_add_data (b, addr, sizeof (addr));
+          }
+          break;
+        default:
+          support_record_failure ();
+          printf ("error: unexpected QTYPE: %s/%u/%u\n",
+                  qname, qclass, qtype);
+        }
+      resolv_response_close_record (b);
+    }
+}
+
+enum output_format
+  {
+    format_get, format_gai
+  };
+
+static void
+format_expected_1 (FILE *out, int family, enum output_format format, int index)
+{
+  for (int i = 0; i < items[index].response; ++i)
+    {
+      char address[200];
+      switch (family)
+        {
+        case AF_INET:
+          snprintf (address, sizeof (address), "10.%d.%d.%d",
+                    index, (i >> 8) & 0xff, i & 0xff);
+          break;
+        case AF_INET6:
+          snprintf (address, sizeof (address), "2001:db8::%x:%x",
+                    index + 1, i + 1);
+          break;
+        default:
+          FAIL_EXIT1 ("unreachable");
+        }
+
+      switch (format)
+        {
+        case format_get:
+          fprintf (out, "address: %s\n", address);
+          break;
+        case format_gai:
+          fprintf (out, "address: STREAM/TCP %s 80\n", address);
+        }
+    }
+}
+
+static char *
+format_expected (const char *fqdn, int family, enum output_format format)
+{
+  int index = find_name (fqdn);
+  TEST_VERIFY_EXIT (index >= 0);
+  struct xmemstream stream;
+  xopen_memstream (&stream);
+
+  TEST_VERIFY_EXIT (items[index].response >= 0);
+  if (format == format_get)
+    fprintf (stream.out, "name: %s\n", items[index].name);
+  if (family == AF_INET || family == AF_UNSPEC)
+    format_expected_1 (stream.out, AF_INET, format, index);
+  if (family == AF_INET6 || family == AF_UNSPEC)
+    format_expected_1 (stream.out, AF_INET6, format, index);
+
+  xfclose_memstream (&stream);
+  return stream.buffer;
+}
+
+static void
+do_get (const char *name, const char *fqdn, int family)
+{
+  char *expected = format_expected (fqdn, family, format_get);
+  if (family == AF_INET)
+    {
+      char *query = xasprintf ("gethostbyname (\"%s\")", name);
+      check_hostent (query, gethostbyname (name), expected);
+      free (query);
+    }
+  char *query = xasprintf ("gethostbyname2 (\"%s\", %d)", name, family);
+  check_hostent (query, gethostbyname2 (name, family), expected);
+
+  /* Test res_search.  */
+  int qtype;
+  switch (family)
+    {
+    case AF_INET:
+      qtype = T_A;
+      break;
+    case AF_INET6:
+      qtype = T_AAAA;
+      break;
+    default:
+      qtype = -1;
+    }
+  if (qtype >= 0)
+    {
+      int sz = 512;
+      unsigned char *response = xmalloc (sz);
+      int ret = res_search (name, C_IN, qtype, response, sz);
+      TEST_VERIFY_EXIT (ret >= 0);
+      if (ret > sz)
+        {
+          /* Truncation.  Retry with a larger buffer.  */
+          sz = 65535;
+          unsigned char *newptr = xrealloc (response, sz);
+          response = newptr;
+
+          ret = res_search (name, C_IN, qtype, response, sz);
+          TEST_VERIFY_EXIT (ret >= 0);
+          TEST_VERIFY_EXIT (ret < sz);
+        }
+      check_dns_packet (query, response, ret, expected);
+      free (response);
+    }
+
+  free (query);
+  free (expected);
+}
+
+static void
+do_gai (const char *name, const char *fqdn, int family)
+{
+  struct addrinfo hints =
+    {
+      .ai_family = family,
+      .ai_protocol = IPPROTO_TCP,
+      .ai_socktype = SOCK_STREAM
+    };
+  struct addrinfo *ai;
+  char *query = xasprintf ("%s:80 [%d]", name, family);
+  int ret = getaddrinfo (name, "80", &hints, &ai);
+  char *expected = format_expected (fqdn, family, format_gai);
+  check_addrinfo (query, ai, ret, expected);
+  if (ret == 0)
+    freeaddrinfo (ai);
+  free (expected);
+  free (query);
+}
+
+static void
+do_both (const char *name, const char *fqdn)
+{
+  do_get (name, fqdn, AF_INET);
+  do_get (name, fqdn, AF_INET6);
+  do_gai (name, fqdn, AF_INET);
+  do_gai (name, fqdn, AF_INET6);
+  do_gai (name, fqdn, AF_UNSPEC);
+}
+
+static void
+do_test_all (bool unconnectable_server)
+{
+  struct resolv_redirect_config config =
+    {
+      .response_callback = response,
+      .search = {"usersys.example.com", "corp.example.com", "example.com"},
+    };
+  struct resolv_test *obj = resolv_test_start (config);
+
+  if (unconnectable_server)
+    {
+      /* 255.255.255.255 results in an immediate connect failure.  The
+         next server will supply the answer instead.  This is a
+         triggering condition for bug 19791.  */
+      _res.nsaddr_list[0].sin_addr.s_addr = -1;
+      _res.nsaddr_list[0].sin_port = htons (53);
+    }
+
+  do_both ("file", "file.corp.example.com");
+  do_both ("www", "www.example.com");
+  do_both ("servfail-usersys", "servfail-usersys.corp.example.com");
+  do_both ("servfail-corp", "servfail-corp.usersys.example.com");
+  do_both ("large", "large.example.com");
+  do_both ("large-servfail", "large-servfail.example.com");
+  do_both ("file.corp", "file.corp");
+
+  /* Check that SERVFAIL and REFUSED responses do not alter the search
+     path resolution.  */
+  rcode_override_server_index = 0;
+  rcode_override = ns_r_servfail;
+  do_both ("hostname", "hostname.usersys.example.com");
+  do_both ("large", "large.example.com");
+  do_both ("large-servfail", "large-servfail.example.com");
+  rcode_override = ns_r_refused;
+  do_both ("hostname", "hostname.usersys.example.com");
+  do_both ("large", "large.example.com");
+  do_both ("large-servfail", "large-servfail.example.com");
+  /* Likewise, but with an NXDOMAIN for the first search path
+     entry.  */
+  rcode_override = ns_r_servfail;
+  do_both ("mail", "mail.corp.example.com");
+  rcode_override = ns_r_refused;
+  do_both ("mail", "mail.corp.example.com");
+  /* Likewise, but with ndots handling.  */
+  rcode_override = ns_r_servfail;
+  do_both ("file.corp", "file.corp");
+  rcode_override = ns_r_refused;
+  do_both ("file.corp", "file.corp");
+
+  resolv_test_end (obj);
+}
+
+static int
+do_test (void)
+{
+  for (int unconnectable_server = 0; unconnectable_server < 2;
+       ++unconnectable_server)
+    do_test_all (unconnectable_server);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/support/Makefile b/support/Makefile
index 9544a08..7e34fcb 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -26,21 +26,42 @@  extra-libs-noinstall := $(extra-libs)
 
 libsupport-routines = \
   check \
+  check_addrinfo \
+  check_dns_packet \
+  check_hostent \
+  check_netent \
   delayed_exit \
   ignore_stderr \
   oom_error \
+  resolv_test \
   set_fortify_handler \
   support_become_root \
   support_enter_network_namespace \
+  support_format_address_family \
+  support_format_addrinfo \
+  support_format_dns_packet \
+  support_format_herrno \
+  support_format_hostent \
+  support_format_netent \
   support_record_failure \
+  support_run_diff \
   support_test_main \
   support_test_verify_impl \
   temp_file \
   write_message \
+  xaccept \
   xasprintf \
+  xbind \
   xcalloc \
+  xconnect \
+  xfclose \
+  xfopen \
   xfork \
+  xgetsockname \
+  xlisten \
   xmalloc \
+  xmemstream \
+  xpoll \
   xpthread_barrier_destroy \
   xpthread_barrier_init \
   xpthread_barrier_wait \
@@ -52,12 +73,18 @@  libsupport-routines = \
   xpthread_join \
   xpthread_mutex_lock \
   xpthread_mutex_unlock \
+  xpthread_once \
   xpthread_sigmask \
   xpthread_spin_lock \
   xpthread_spin_unlock \
   xrealloc \
+  xrecvfrom \
+  xsendto \
+  xsetsockopt \
   xsocket \
+  xstrdup \
   xwaitpid \
+  xwrite \
 
 libsupport-static-only-routines := $(libsupport-routines)
 # Only build one variant of the library.
diff --git a/support/check_addrinfo.c b/support/check_addrinfo.c
new file mode 100644
index 0000000..793b34f
--- /dev/null
+++ b/support/check_addrinfo.c
@@ -0,0 +1,42 @@ 
+/* Compare struct addrinfo values against a formatted string.
+   Copyright (C) 2016 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 <support/check_nss.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/run_diff.h>
+
+void
+check_addrinfo (const char *query_description, struct addrinfo *ai, int ret,
+                const char *expected)
+{
+  char *formatted = support_format_addrinfo (ai, ret);
+  if (strcmp (formatted, expected) != 0)
+    {
+      support_record_failure ();
+      printf ("error: addrinfo comparison failure\n");
+      if (query_description != NULL)
+        printf ("query: %s\n", query_description);
+      support_run_diff ("expected", expected,
+                        "actual", formatted);
+    }
+  free (formatted);
+}
diff --git a/support/check_dns_packet.c b/support/check_dns_packet.c
new file mode 100644
index 0000000..a2c16b1
--- /dev/null
+++ b/support/check_dns_packet.c
@@ -0,0 +1,42 @@ 
+/* Check that a DNS packet buffer has the expected contents.
+   Copyright (C) 2016 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 <support/check_nss.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/run_diff.h>
+
+void
+check_dns_packet (const char *query_description,
+                  const unsigned char *buffer, size_t length,
+                  const char *expected)
+{
+  char *formatted = support_format_dns_packet (buffer, length);
+  if (strcmp (formatted, expected) != 0)
+    {
+      support_record_failure ();
+      printf ("error: packet comparison failure\n");
+      if (query_description != NULL)
+        printf ("query: %s\n", query_description);
+      support_run_diff ("expected", expected, "actual", formatted);
+    }
+  free (formatted);
+}
diff --git a/support/check_hostent.c b/support/check_hostent.c
new file mode 100644
index 0000000..b0661b1
--- /dev/null
+++ b/support/check_hostent.c
@@ -0,0 +1,42 @@ 
+/* Compare struct hostent values against a formatted string.
+   Copyright (C) 2016 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 <support/check_nss.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/run_diff.h>
+
+void
+check_hostent (const char *query_description, struct hostent *h,
+               const char *expected)
+{
+  char *formatted = support_format_hostent (h);
+  if (strcmp (formatted, expected) != 0)
+    {
+      support_record_failure ();
+      printf ("error: hostent comparison failure\n");
+      if (query_description != NULL)
+        printf ("query: %s\n", query_description);
+      support_run_diff ("expected", expected,
+                        "actual", formatted);
+    }
+  free (formatted);
+}
diff --git a/support/check_netent.c b/support/check_netent.c
new file mode 100644
index 0000000..f208624
--- /dev/null
+++ b/support/check_netent.c
@@ -0,0 +1,42 @@ 
+/* Compare struct netent values against a formatted string.
+   Copyright (C) 2016 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 <support/check_nss.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/format_nss.h>
+#include <support/run_diff.h>
+
+void
+check_netent (const char *query_description, struct netent *e,
+              const char *expected)
+{
+  char *formatted = support_format_netent (e);
+  if (strcmp (formatted, expected) != 0)
+    {
+      support_record_failure ();
+      printf ("error: netent comparison failure\n");
+      if (query_description != NULL)
+        printf ("query: %s\n", query_description);
+      support_run_diff ("expected", expected,
+                        "actual", formatted);
+    }
+  free (formatted);
+}
diff --git a/support/check_nss.h b/support/check_nss.h
new file mode 100644
index 0000000..6ab2824
--- /dev/null
+++ b/support/check_nss.h
@@ -0,0 +1,42 @@ 
+/* Test verification functions for NSS- and DNS-related data.
+   Copyright (C) 2016 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 SUPPORT_CHECK_NSS_H
+#define SUPPORT_CHECK_NSS_H
+
+#include <netdb.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* Compare the data structures against the expected values (which have
+   to be formatted according to the support_format_* functions in
+   <support/format_nss.h>).  If there is a difference, a delayed test
+   failure is recorded, and a diff is written to standard output.  */
+void check_addrinfo (const char *query_description,
+                     struct addrinfo *, int ret, const char *expected);
+void check_dns_packet (const char *query_description,
+                       const unsigned char *, size_t, const char *expected);
+void check_hostent (const char *query_description,
+                    struct hostent *, const char *expected);
+void check_netent (const char *query_description,
+                   struct netent *, const char *expected);
+
+__END_DECLS
+
+#endif  /* SUPPORT_CHECK_NSS_H */
diff --git a/support/format_nss.h b/support/format_nss.h
new file mode 100644
index 0000000..07e32f5
--- /dev/null
+++ b/support/format_nss.h
@@ -0,0 +1,41 @@ 
+/* String formatting functions for NSS- and DNS-related data.
+   Copyright (C) 2016 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 SUPPORT_FORMAT_NSS_H
+#define SUPPORT_FORMAT_NSS_H
+
+#include <netdb.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* The following functions format their arguments as human-readable
+   strings (which can span multiple lines).  The caller must free the
+   returned buffer.  For NULL pointers or failure status arguments,
+   error variables such as h_errno and errno are included in the
+   result.  */
+char *support_format_address_family (int);
+char *support_format_addrinfo (struct addrinfo *, int ret);
+char *support_format_dns_packet (const unsigned char *buffer, size_t length);
+char *support_format_herrno (int);
+char *support_format_hostent (struct hostent *);
+char *support_format_netent (struct netent *);
+
+__END_DECLS
+
+#endif  /* SUPPORT_FORMAT_NSS_H */
diff --git a/support/resolv_test.c b/support/resolv_test.c
new file mode 100644
index 0000000..80d8c74
--- /dev/null
+++ b/support/resolv_test.c
@@ -0,0 +1,1150 @@ 
+/* DNS test framework and libresolv redirection.
+   Copyright (C) 2016 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 <support/resolv_test.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <nss.h>
+#include <resolv.h>
+#include <search.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/namespace.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+#include <support/xsocket.h>
+#include <support/xthread.h>
+#include <unistd.h>
+
+/* Response builder. */
+
+enum
+  {
+    max_response_length = 65536
+  };
+
+/* List of pointers to be freed.  The hash table implementation
+   (struct hsearch_data) does not provide a way to deallocate all
+   objects, so this approach is used to avoid memory leaks.  */
+struct to_be_freed
+{
+  struct to_be_freed *next;
+  void *ptr;
+};
+
+struct resolv_response_builder
+{
+  const unsigned char *query_buffer;
+  size_t query_length;
+
+  size_t offset;                /* Bytes written so far in buffer.  */
+  ns_sect section;              /* Current section in the DNS packet.  */
+  unsigned int truncate_bytes;  /* Bytes to remove at end of response. */
+  bool drop;                    /* Discard generated response.  */
+  bool close;                   /* Close TCP client connection.  */
+
+  /* Offset of the two-byte RDATA length field in the currently
+     written RDATA sub-structure.  0 if no RDATA is being written.  */
+  size_t current_rdata_offset;
+
+  /* Hash table for locating targets for label compression.  */
+  struct hsearch_data compression_offsets;
+  /* List of pointers which need to be freed.  Used for domain names
+     involved in label compression.  */
+  struct to_be_freed *to_be_freed;
+
+  /* Must be last.  Not zeroed for performance reasons.  */
+  unsigned char buffer[max_response_length];
+};
+
+/* Response builder. */
+
+/* Add a pointer to the list of pointers to be freed when B is
+   deallocated.  */
+static void
+response_push_pointer_to_free (struct resolv_response_builder *b, void *ptr)
+{
+  if (ptr == NULL)
+    return;
+  struct to_be_freed *e = xmalloc (sizeof (*e));
+  *e = (struct to_be_freed) {b->to_be_freed, ptr};
+  b->to_be_freed = e;
+}
+
+void
+resolv_response_init (struct resolv_response_builder *b,
+                      struct resolv_response_flags flags)
+{
+  if (b->offset > 0)
+    FAIL_EXIT1 ("response_init: called at offset %zu", b->offset);
+  if (b->query_length < 12)
+    FAIL_EXIT1 ("response_init called for a query of size %zu",
+                b->query_length);
+  if (flags.rcode > 15)
+    FAIL_EXIT1 ("response_init: invalid RCODE %u", flags.rcode);
+
+  /* Copy the transaction ID.  */
+  b->buffer[0] = b->query_buffer[0];
+  b->buffer[1] = b->query_buffer[1];
+
+  /* Initialize the flags.  */
+  b->buffer[2] = 0x80;                       /* Mark as response.   */
+  b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit.  */
+  if (flags.tc)
+    b->buffer[2] |= 0x02;
+  b->buffer[3] = 0x80 | flags.rcode; /* Always set RA.  */
+
+  /* Fill in the initial section count values.  */
+  b->buffer[4] = flags.qdcount >> 8;
+  b->buffer[5] = flags.qdcount;
+  b->buffer[6] = flags.ancount >> 8;
+  b->buffer[7] = flags.ancount;
+  b->buffer[8] = flags.nscount >> 8;
+  b->buffer[9] = flags.nscount;
+  b->buffer[10] = flags.adcount >> 8;
+  b->buffer[11] = flags.adcount;
+
+  b->offset = 12;
+}
+
+void
+resolv_response_section (struct resolv_response_builder *b, ns_sect section)
+{
+  if (b->offset == 0)
+    FAIL_EXIT1 ("resolv_response_section: response_init not called before");
+  if (section < b->section)
+    FAIL_EXIT1 ("resolv_response_section: cannot go back to previous section");
+  b->section = section;
+}
+
+/* Add a single byte to B.  */
+static inline void
+response_add_byte (struct resolv_response_builder *b, unsigned char ch)
+{
+  if (b->offset == max_response_length)
+    FAIL_EXIT1 ("DNS response exceeds 64 KiB limit");
+  b->buffer[b->offset] = ch;
+  ++b->offset;
+}
+
+/* Add a 16-bit word VAL to B, in big-endian format.  */
+static void
+response_add_16 (struct resolv_response_builder *b, uint16_t val)
+{
+  response_add_byte (b, val >> 8);
+  response_add_byte (b, val);
+}
+
+/* Increment the pers-section record counter in the packet header.  */
+static void
+response_count_increment (struct resolv_response_builder *b)
+{
+  unsigned int offset = b->section;
+  offset = 4 + 2 * offset;
+  ++b->buffer[offset + 1];
+  if (b->buffer[offset + 1] == 0)
+    {
+      /* Carry.  */
+      ++b->buffer[offset];
+      if (b->buffer[offset] == 0)
+        /* Overflow.  */
+        FAIL_EXIT1 ("too many records in section");
+    }
+}
+
+void
+resolv_response_add_question (struct resolv_response_builder *b,
+                              const char *name, uint16_t class, uint16_t type)
+{
+  if (b->offset == 0)
+    FAIL_EXIT1 ("resolv_response_add_question: "
+                "resolv_response_init not called");
+  if (b->section != ns_s_qd)
+    FAIL_EXIT1 ("resolv_response_add_question: "
+                "must be called in the question section");
+
+  resolv_response_add_name (b, name);
+  response_add_16 (b, type);
+  response_add_16 (b, class);
+
+  response_count_increment (b);
+}
+
+void
+resolv_response_add_name (struct resolv_response_builder *b,
+                          const char *const origname)
+{
+  /* Normalized name.  */
+  char *name;
+  /* Normalized name with case preserved.  */
+  char *name_case;
+  {
+    size_t namelen = strlen (origname);
+    /* Remove trailing dots.  FIXME: Handle trailing quoted dots.  */
+    while (namelen > 0 && origname[namelen - 1] == '.')
+      --namelen;
+    name = xmalloc (namelen + 1);
+    name_case = xmalloc (namelen + 1);
+    /* Copy and convert to lowercase.  FIXME: This needs to normalize
+       escaping as well.  */
+    for (size_t i = 0; i < namelen; ++i)
+      {
+        char ch = origname[i];
+        name_case[i] = ch;
+        if ('A' <= ch && ch <= 'Z')
+          ch = ch - 'A' + 'a';
+        name[i] = ch;
+      }
+    name[namelen] = 0;
+    name_case[namelen] = 0;
+  }
+  char *name_start = name;
+  char *name_case_start = name_case;
+
+  bool compression = false;
+  while (*name)
+    {
+      /* Search for a previous name we can reference.  */
+      ENTRY new_entry =
+        {
+          .key = name,
+          .data = (void *) (uintptr_t) b->offset,
+        };
+
+      /* If the label can be a compression target because it is at a
+         reachable offset, add it to the hash table.  */
+      ACTION action;
+      if (b->offset < (1 << 12))
+        action = ENTER;
+      else
+        action = FIND;
+
+      /* Search for known compression offsets in the hash table.  */
+      ENTRY *e;
+      if (hsearch_r (new_entry, action, &e, &b->compression_offsets) == 0)
+        {
+          if (action == FIND && errno == ESRCH)
+            /* Fall through.  */
+            e = NULL;
+          else
+            FAIL_EXIT1 ("hsearch_r failure in name compression: %m");
+        }
+
+      /* The name is known.  Reference the previous location.  */
+      if (e != NULL && e->data != new_entry.data)
+        {
+          size_t old_offset = (uintptr_t) e->data;
+          response_add_byte (b, 0xC0 | (old_offset >> 8));
+          response_add_byte (b, old_offset);
+          compression = true;
+          break;
+        }
+
+      /* The name does not exist yet.  Write one label.  First, add
+         room for the label length.  */
+      size_t buffer_label_offset = b->offset;
+      response_add_byte (b, 0);
+
+      /* Copy the label.  */
+      while (true)
+        {
+          char ch = *name_case;
+          if (ch == '\0')
+            break;
+          ++name;
+          ++name_case;
+          if (ch == '.')
+            break;
+          /* FIXME: Handle escaping.  */
+          response_add_byte (b, ch);
+        }
+
+      /* Patch in the label length.  */
+      size_t label_length = b->offset - buffer_label_offset - 1;
+      if (label_length == 0)
+        FAIL_EXIT1 ("empty label in name compression: %s", origname);
+      if (label_length > 63)
+        FAIL_EXIT1 ("label too long in name compression: %s", origname);
+      b->buffer[buffer_label_offset] = label_length;
+
+      /* Continue with the tail of the name and the next label.  */
+    }
+
+  if (compression)
+    {
+      /* If we found an immediate match for the name, we have not put
+         it into the hash table, and can free it immediately.  */
+      if (name == name_start)
+        free (name_start);
+      else
+        response_push_pointer_to_free (b, name_start);
+    }
+  else
+    {
+      /* Terminate the sequence of labels.  With compression, this is
+         implicit in the compression reference.  */
+      response_add_byte (b, 0);
+      response_push_pointer_to_free (b, name_start);
+    }
+
+  free (name_case_start);
+}
+
+void
+resolv_response_open_record (struct resolv_response_builder *b,
+                             const char *name,
+                             uint16_t class, uint16_t type, uint32_t ttl)
+{
+  if (b->section == ns_s_qd)
+    FAIL_EXIT1 ("resolv_response_open_record called in question section");
+  if (b->current_rdata_offset != 0)
+    FAIL_EXIT1 ("resolv_response_open_record called with open record");
+
+  resolv_response_add_name (b, name);
+  response_add_16 (b, type);
+  response_add_16 (b, class);
+  response_add_16 (b, ttl >> 16);
+  response_add_16 (b, ttl);
+
+  b->current_rdata_offset = b->offset;
+  /* Add room for the RDATA length.  */
+  response_add_16 (b, 0);
+}
+
+
+void
+resolv_response_close_record (struct resolv_response_builder *b)
+{
+  size_t rdata_offset = b->current_rdata_offset;
+  if (rdata_offset == 0)
+    FAIL_EXIT1 ("response_close_record called without open record");
+  size_t rdata_length = b->offset - rdata_offset - 2;
+  if (rdata_length > 65535)
+    FAIL_EXIT1 ("RDATA length %zu exceeds limit", rdata_length);
+  b->buffer[rdata_offset] = rdata_length >> 8;
+  b->buffer[rdata_offset + 1] = rdata_length;
+  response_count_increment (b);
+  b->current_rdata_offset = 0;
+}
+
+void
+resolv_response_add_data (struct resolv_response_builder *b,
+                          const void *data, size_t length)
+{
+  size_t remaining = max_response_length - b->offset;
+  if (remaining < length)
+    FAIL_EXIT1 ("resolv_response_add_data: not enough room for %zu bytes",
+                length);
+  memcpy (b->buffer + b->offset, data, length);
+  b->offset += length;
+}
+
+void
+resolv_response_drop (struct resolv_response_builder *b)
+{
+  b->drop = true;
+}
+
+void
+resolv_response_close (struct resolv_response_builder *b)
+{
+  b->close = true;
+}
+
+void
+resolv_response_truncate_data (struct resolv_response_builder *b, size_t count)
+{
+  if (count > 65535)
+    FAIL_EXIT1 ("resolv_response_truncate_data: argument too large: %zu",
+                count);
+  b->truncate_bytes = count;
+}
+
+
+size_t
+resolv_response_length (const struct resolv_response_builder *b)
+{
+  return b->offset;
+}
+
+unsigned char *
+resolv_response_buffer (const struct resolv_response_builder *b)
+{
+  unsigned char *result = xmalloc (b->offset);
+  memcpy (result, b->buffer, b->offset);
+  return result;
+}
+
+static struct resolv_response_builder *
+response_builder_allocate
+  (const unsigned char *query_buffer, size_t query_length)
+{
+  struct resolv_response_builder *b = xmalloc (sizeof (*b));
+  memset (b, 0, offsetof (struct resolv_response_builder, buffer));
+  b->query_buffer = query_buffer;
+  b->query_length = query_length;
+  TEST_VERIFY_EXIT (hcreate_r (10000, &b->compression_offsets) != 0);
+  return b;
+}
+
+static void
+response_builder_free (struct resolv_response_builder *b)
+{
+  struct to_be_freed *current = b->to_be_freed;
+  while (current != NULL)
+    {
+      struct to_be_freed *next = current->next;
+      free (current->ptr);
+      free (current);
+      current = next;
+    }
+  hdestroy_r (&b->compression_offsets);
+  free (b);
+}
+
+/* DNS query processing. */
+
+/* Data extracted from the question section of a DNS packet.  */
+struct query_info
+{
+  char qname[MAXDNAME];
+  uint16_t qclass;
+  uint16_t qtype;
+};
+
+/* Update *INFO from the specified DNS packet.  */
+static void
+parse_query (struct query_info *info,
+             const unsigned char *buffer, size_t length)
+{
+  if (length < 12)
+    FAIL_EXIT1 ("malformed DNS query: too short: %zu bytes", length);
+
+  int ret = dn_expand (buffer, buffer + length, buffer + 12,
+                       info->qname, sizeof (info->qname));
+  if (ret < 0)
+    FAIL_EXIT1 ("malformed DNS query: cannot uncompress QNAME");
+
+  /* Obtain QTYPE and QCLASS.  */
+  size_t remaining = length - (12 + ret);
+  struct
+  {
+    uint16_t qtype;
+    uint16_t qclass;
+  } qtype_qclass;
+  if (remaining < sizeof (qtype_qclass))
+    FAIL_EXIT1 ("malformed DNS query: "
+                "query lacks QCLASS/QTYPE, QNAME: %s", info->qname);
+  memcpy (&qtype_qclass, buffer + 12 + ret, sizeof (qtype_qclass));
+  info->qclass = ntohs (qtype_qclass.qclass);
+  info->qtype = ntohs (qtype_qclass.qtype);
+}
+
+
+/* Main testing framework.  */
+
+/* Per-server information.  One struct is allocated for each test
+   server.  */
+struct resolv_test_server
+{
+  /* Local address of the server.  UDP and TCP use the same port.  */
+  struct sockaddr_in address;
+
+  /* File descriptor of the UDP server, or -1 if this server is
+     disabled.  */
+  int socket_udp;
+
+  /* File descriptor of the TCP server, or -1 if this server is
+     disabled.  */
+  int socket_tcp;
+
+  /* Counter of the number of responses processed so far.  */
+  size_t response_number;
+
+  /* Thread handles for the server threads (if not disabled in the
+     configuration).  */
+  pthread_t thread_udp;
+  pthread_t thread_tcp;
+};
+
+/* Main struct for keeping track of libresolv redirection and
+   testing.  */
+struct resolv_test
+{
+  /* After initialization, any access to the struct must be performed
+     while this lock is acquired.  */
+  pthread_mutex_t lock;
+
+  /* Data for each test server. */
+  struct resolv_test_server servers[resolv_max_test_servers];
+
+  /* Used if config.single_thread_udp is true.  */
+  pthread_t thread_udp_single;
+
+  struct resolv_redirect_config config;
+  bool termination_requested;
+};
+
+/* Function implementing a server thread.  */
+typedef void (*thread_callback) (struct resolv_test *, int server_index);
+
+/* Storage for thread-specific data, for passing to the
+   thread_callback function.  */
+struct thread_closure
+{
+  struct resolv_test *obj;      /* Current test object.  */
+  thread_callback callback;     /* Function to call.  */
+  int server_index;             /* Index of the implemented server.  */
+};
+
+/* Wrap response_callback as a function which can be passed to
+   pthread_create.  */
+static void *
+thread_callback_wrapper (void *arg)
+{
+  struct thread_closure *closure = arg;
+  closure->callback (closure->obj, closure->server_index);
+  free (closure);
+  return NULL;
+}
+
+/* Start a server thread for the specified SERVER_INDEX, implemented
+   by CALLBACK.  */
+static pthread_t
+start_server_thread (struct resolv_test *obj, int server_index,
+                     thread_callback callback)
+{
+  struct thread_closure *closure = xmalloc (sizeof (*closure));
+  *closure = (struct thread_closure)
+    {
+      .obj = obj,
+      .callback = callback,
+      .server_index = server_index,
+    };
+  return xpthread_create (NULL, thread_callback_wrapper, closure);
+}
+
+/* Process one UDP query.  Return false if a termination requested has
+   been detected.  */
+static bool
+server_thread_udp_process_one (struct resolv_test *obj, int server_index)
+{
+  unsigned char query[512];
+  struct sockaddr_storage peer;
+  socklen_t peerlen = sizeof (peer);
+  size_t length = xrecvfrom (obj->servers[server_index].socket_udp,
+                             query, sizeof (query), 0,
+                             (struct sockaddr *) &peer, &peerlen);
+  /* Check for termination.  */
+  {
+    bool termination_requested;
+    xpthread_mutex_lock (&obj->lock);
+    termination_requested = obj->termination_requested;
+    xpthread_mutex_unlock (&obj->lock);
+    if (termination_requested)
+      return false;
+  }
+
+
+  struct query_info qinfo;
+  parse_query (&qinfo, query, length);
+  if (test_verbose > 0)
+    {
+      if (test_verbose > 1)
+        printf ("info: UDP server %d: incoming query:"
+                " %zd bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
+                server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype,
+                query[0], query[1]);
+      else
+        printf ("info: UDP server %d: incoming query:"
+                " %zd bytes, %s/%u/%u\n",
+                server_index, length, qinfo.qname, qinfo.qclass, qinfo.qtype);
+    }
+
+  struct resolv_response_context ctx =
+    {
+      .query_buffer = query,
+      .query_length = length,
+      .server_index = server_index,
+      .tcp = false,
+    };
+  struct resolv_response_builder *b = response_builder_allocate (query, length);
+  obj->config.response_callback
+    (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
+
+  if (b->drop)
+    {
+      if (test_verbose)
+        printf ("info: UDP server %d: dropping response to %s/%u/%u\n",
+                server_index, qinfo.qname, qinfo.qclass, qinfo.qtype);
+    }
+  else
+    {
+      if (test_verbose)
+        {
+          if (b->offset >= 12)
+            printf ("info: UDP server %d: sending response:"
+                    " %zu bytes, RCODE %d (for %s/%u/%u)\n",
+                    server_index, b->offset, b->buffer[3] & 0x0f,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype);
+          else
+            printf ("info: UDP server %d: sending response: %zu bytes"
+                    " (for %s/%u/%u)\n",
+                    server_index, b->offset,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype);
+          if (b->truncate_bytes > 0)
+            printf ("info:    truncated by %u bytes\n", b->truncate_bytes);
+        }
+      size_t to_send = b->offset;
+      if (to_send < b->truncate_bytes)
+        to_send = 0;
+      else
+        to_send -= b->truncate_bytes;
+
+      /* Ignore most errors here because the other end may have closed
+         the socket. */
+      if (sendto (obj->servers[server_index].socket_udp,
+                  b->buffer, to_send, 0,
+                  (struct sockaddr *) &peer, peerlen) < 0)
+        TEST_VERIFY_EXIT (errno != EBADF);
+    }
+  response_builder_free (b);
+  return true;
+}
+
+/* UDP thread_callback function.  Variant for one thread per
+   server.  */
+static void
+server_thread_udp (struct resolv_test *obj, int server_index)
+{
+  while (server_thread_udp_process_one (obj, server_index))
+    ;
+}
+
+/* Single-threaded UDP processing function, for the single_thread_udp
+   case.  */
+static void *
+server_thread_udp_single (void *closure)
+{
+  struct resolv_test *obj = closure;
+
+  struct pollfd fds[resolv_max_test_servers];
+  for (int server_index = 0; server_index < resolv_max_test_servers;
+       ++server_index)
+    if (obj->config.servers[server_index].disable_udp)
+      fds[server_index] = (struct pollfd) {.fd = -1};
+    else
+      {
+        fds[server_index] = (struct pollfd)
+          {
+            .fd = obj->servers[server_index].socket_udp,
+            .events = POLLIN
+          };
+
+        /* Make the socket non-blocking.  */
+        int flags = fcntl (obj->servers[server_index].socket_udp, F_GETFL, 0);
+        if (flags < 0)
+          FAIL_EXIT1 ("fcntl (F_GETFL): %m");
+        flags |= O_NONBLOCK;
+        if (fcntl (obj->servers[server_index].socket_udp, F_SETFL, flags) < 0)
+          FAIL_EXIT1 ("fcntl (F_SETFL): %m");
+      }
+
+  while (true)
+    {
+      xpoll (fds, resolv_max_test_servers, -1);
+      for (int server_index = 0; server_index < resolv_max_test_servers;
+           ++server_index)
+        if (fds[server_index].revents != 0)
+          {
+            if (!server_thread_udp_process_one (obj, server_index))
+              goto out;
+            fds[server_index].revents = 0;
+          }
+    }
+
+ out:
+  return NULL;
+}
+
+/* Start the single UDP handler thread (for the single_thread_udp
+   case).  */
+static void
+start_server_thread_udp_single (struct resolv_test *obj)
+{
+  obj->thread_udp_single
+    = xpthread_create (NULL, server_thread_udp_single, obj);
+}
+
+/* Data describing a TCP client connect.  */
+struct tcp_thread_closure
+{
+  struct resolv_test *obj;
+  int server_index;
+  int client_socket;
+};
+
+/* Read a complete DNS query packet.  If EOF_OK, an immediate
+   end-of-file condition is acceptable.  */
+static bool
+read_fully (int fd, void *buf, size_t len, bool eof_ok)
+{
+  const void *const end = buf + len;
+  while (buf < end)
+    {
+      ssize_t ret = read (fd, buf, end - buf);
+      if (ret == 0)
+        {
+          if (!eof_ok)
+            {
+              support_record_failure ();
+              printf ("error: unexpected EOF on TCP connection\n");
+            }
+          return false;
+        }
+      else if (ret < 0)
+        {
+          if (!eof_ok || errno != ECONNRESET)
+            {
+              support_record_failure ();
+              printf ("error: TCP read: %m\n");
+            }
+          return false;
+        }
+      buf += ret;
+      eof_ok = false;
+    }
+  return true;
+}
+
+/* Write an array of iovecs.  Terminate the process on failure.  */
+static void
+writev_fully (int fd, struct iovec *buffers, size_t count)
+{
+  while (count > 0)
+    {
+      /* Skip zero-length write requests.  */
+      if (buffers->iov_len == 0)
+        {
+          ++buffers;
+          --count;
+          continue;
+        }
+      /* Try to rewrite the remaing buffers.  */
+      ssize_t ret = writev (fd, buffers, count);
+      if (ret < 0)
+        FAIL_EXIT1 ("writev: %m");
+      if (ret == 0)
+        FAIL_EXIT1 ("writev: invalid return value zero");
+      /* Find the buffers that were successfully written.  */
+      while (ret > 0)
+        {
+          if (count == 0)
+            FAIL_EXIT1 ("internal writev consistency failure");
+          /* Current buffer was partially written.  */
+          if (buffers->iov_len > (size_t) ret)
+            {
+              buffers->iov_base += ret;
+              buffers->iov_len -= ret;
+              ret = 0;
+            }
+          else
+            {
+              ret -= buffers->iov_len;
+              buffers->iov_len = 0;
+              ++buffers;
+              --count;
+            }
+        }
+    }
+}
+
+/* Thread callback for handling a single established TCP connection to
+   a client.  */
+static void *
+server_thread_tcp_client (void *arg)
+{
+  struct tcp_thread_closure *closure = arg;
+
+  while (true)
+    {
+      /* Read packet length.  */
+      uint16_t query_length;
+      if (!read_fully (closure->client_socket,
+                       &query_length, sizeof (query_length), true))
+        break;
+      query_length = ntohs (query_length);
+
+      /* Read the packet.  */
+      unsigned char *query_buffer = xmalloc (query_length);
+      read_fully (closure->client_socket, query_buffer, query_length, false);
+
+      struct query_info qinfo;
+      parse_query (&qinfo, query_buffer, query_length);
+      if (test_verbose > 0)
+        {
+          if (test_verbose > 1)
+            printf ("info: UDP server %d: incoming query:"
+                    " %d bytes, %s/%u/%u, tnxid=0x%02x%02x\n",
+                    closure->server_index, query_length,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype,
+                    query_buffer[0], query_buffer[1]);
+          else
+            printf ("info: TCP server %d: incoming query:"
+                    " %u bytes, %s/%u/%u\n",
+                    closure->server_index, query_length,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype);
+        }
+
+      struct resolv_response_context ctx =
+        {
+          .query_buffer = query_buffer,
+          .query_length = query_length,
+          .server_index = closure->server_index,
+          .tcp = true,
+        };
+      struct resolv_response_builder *b = response_builder_allocate
+        (query_buffer, query_length);
+      closure->obj->config.response_callback
+        (&ctx, b, qinfo.qname, qinfo.qclass, qinfo.qtype);
+
+      if (b->drop)
+        {
+          if (test_verbose)
+            printf ("info: TCP server %d: dropping response to %s/%u/%u\n",
+                    closure->server_index,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype);
+        }
+      else
+        {
+          if (test_verbose)
+            printf ("info: TCP server %d: sending response: %zu bytes"
+                    " (for %s/%u/%u)\n",
+                    closure->server_index, b->offset,
+                    qinfo.qname, qinfo.qclass, qinfo.qtype);
+          uint16_t length = htons (b->offset);
+          size_t to_send = b->offset;
+          if (to_send < b->truncate_bytes)
+            to_send = 0;
+          else
+            to_send -= b->truncate_bytes;
+          struct iovec buffers[2] =
+            {
+              {&length, sizeof (length)},
+              {b->buffer, to_send}
+            };
+          writev_fully (closure->client_socket, buffers, 2);
+        }
+      bool close_flag = b->close;
+      response_builder_free (b);
+      free (query_buffer);
+      if (close_flag)
+        break;
+    }
+
+  close (closure->client_socket);
+  free (closure);
+  return NULL;
+}
+
+/* thread_callback for the TCP case.  Accept connections and create a
+   new thread for each client.  */
+static void
+server_thread_tcp (struct resolv_test *obj, int server_index)
+{
+  while (true)
+    {
+      /* Get the client conenction.  */
+      int client_socket = xaccept
+        (obj->servers[server_index].socket_tcp, NULL, NULL);
+
+      /* Check for termination.  */
+      xpthread_mutex_lock (&obj->lock);
+      if (obj->termination_requested)
+        {
+          xpthread_mutex_unlock (&obj->lock);
+          close (client_socket);
+          break;
+        }
+      xpthread_mutex_unlock (&obj->lock);
+
+      /* Spawn a new thread for handling this connection.  */
+      struct tcp_thread_closure *closure = xmalloc (sizeof (*closure));
+      *closure = (struct tcp_thread_closure)
+        {
+          .obj = obj,
+          .server_index = server_index,
+          .client_socket = client_socket,
+        };
+
+      pthread_t thr
+        = xpthread_create (NULL, server_thread_tcp_client, closure);
+      /* TODO: We should keep track of this thread so that we can
+         block in resolv_test_end until it has exited.  */
+      xpthread_detach (thr);
+    }
+}
+
+/* Create UDP and TCP server sockets.  */
+static void
+make_server_sockets (struct resolv_test_server *server)
+{
+  while (true)
+    {
+      server->socket_udp = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+      server->socket_tcp = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+
+      /* Pick the address for the UDP socket.  */
+      server->address = (struct sockaddr_in)
+        {
+          .sin_family = AF_INET,
+          .sin_addr = {.s_addr = htonl (INADDR_LOOPBACK)}
+        };
+      xbind (server->socket_udp,
+             (struct sockaddr *)&server->address, sizeof (server->address));
+
+      /* Retrieve the address. */
+      socklen_t addrlen = sizeof (server->address);
+      xgetsockname (server->socket_udp,
+                    (struct sockaddr *)&server->address, &addrlen);
+
+      /* Bind the TCP socket to the same address.  */
+      {
+        int on = 1;
+        xsetsockopt (server->socket_tcp, SOL_SOCKET, SO_REUSEADDR,
+                     &on, sizeof (on));
+      }
+      if (bind (server->socket_tcp,
+                (struct sockaddr *)&server->address,
+                sizeof (server->address)) != 0)
+        {
+          /* Port collision.  The UDP bind succeeded, but the TCP BIND
+             failed.  We assume here that the kernel will pick the
+             next local UDP address randomly.  */
+          if (errno == EADDRINUSE)
+            {
+              close (server->socket_udp);
+              close (server->socket_tcp);
+              continue;
+            }
+          FAIL_EXIT1 ("TCP bind: %m");
+        }
+      xlisten (server->socket_tcp, 5);
+      break;
+    }
+}
+
+/* One-time initialization of NSS.  */
+static void
+resolv_redirect_once (void)
+{
+  /* Only use nss_dns.  */
+  __nss_configure_lookup ("hosts", "dns");
+  __nss_configure_lookup ("networks", "dns");
+  /* Enter a network namespace for isolation and firewall state
+     cleanup.  The tests will still work if these steps fail, but they
+     may be less reliable.  */
+  support_become_root ();
+  support_enter_network_namespace ();
+}
+pthread_once_t resolv_redirect_once_var = PTHREAD_ONCE_INIT;
+
+void
+resolv_test_init (void)
+{
+  /* Perform one-time initialization of NSS.  */
+  xpthread_once (&resolv_redirect_once_var, resolv_redirect_once);
+}
+
+/* Copy the search path from CONFIG.search to the _res object.  */
+static void
+set_search_path (struct resolv_redirect_config config)
+{
+  memset (_res.defdname, 0, sizeof (_res.defdname));
+  memset (_res.dnsrch, 0, sizeof (_res.dnsrch));
+
+  char *current = _res.defdname;
+  char *end = current + sizeof (_res.defdname);
+
+  for (unsigned int i = 0;
+       i < sizeof (config.search) / sizeof (config.search[0]); ++i)
+    {
+      if (config.search[i] == NULL)
+        continue;
+
+      size_t length = strlen (config.search[i]) + 1;
+      size_t remaining = end - current;
+      TEST_VERIFY_EXIT (length <= remaining);
+      memcpy (current, config.search[i], length);
+      _res.dnsrch[i] = current;
+      current += length;
+    }
+}
+
+struct resolv_test *
+resolv_test_start (struct resolv_redirect_config config)
+{
+  /* Apply configuration defaults.  */
+  if (config.nscount == 0)
+    config.nscount = resolv_max_test_servers;
+
+  struct resolv_test *obj = xmalloc (sizeof (*obj));
+  *obj = (struct resolv_test) {
+    .config = config,
+    .lock = PTHREAD_MUTEX_INITIALIZER,
+  };
+
+  resolv_test_init ();
+
+  /* Create all the servers, to reserve the necessary ports.  */
+  for (int server_index = 0; server_index < config.nscount; ++server_index)
+    make_server_sockets (obj->servers + server_index);
+
+  /* Start server threads.  Disable the server ports, as
+     requested.  */
+  for (int server_index = 0; server_index < config.nscount; ++server_index)
+    {
+      struct resolv_test_server *server = obj->servers + server_index;
+      if (config.servers[server_index].disable_udp)
+        {
+          close (server->socket_udp);
+          server->socket_udp = -1;
+        }
+      else if (!config.single_thread_udp)
+        server->thread_udp = start_server_thread (obj, server_index,
+                                                  server_thread_udp);
+      if (config.servers[server_index].disable_tcp)
+        {
+          close (server->socket_tcp);
+          server->socket_tcp = -1;
+        }
+      else
+        server->thread_tcp = start_server_thread (obj, server_index,
+                                                  server_thread_tcp);
+    }
+  if (config.single_thread_udp)
+    start_server_thread_udp_single (obj);
+
+  int timeout = 1;
+
+  /* Initialize libresolv.  */
+  TEST_VERIFY_EXIT (res_init () == 0);
+
+  /* Disable IPv6 name server addresses.  The code below only
+     overrides the IPv4 addresses.  */
+  __res_iclose (&_res, true);
+  _res._u._ext.nscount = 0;
+
+  /* Redirect queries to the server socket.  */
+  if (test_verbose)
+    {
+      printf ("info: old timeout value: %d\n", _res.retrans);
+      printf ("info: old retry attempt value: %d\n", _res.retry);
+      printf ("info: old _res.options: 0x%lx\n", _res.options);
+      printf ("info: old _res.nscount value: %d\n", _res.nscount);
+      printf ("info: old _res.ndots value: %d\n", _res.ndots);
+    }
+  _res.retrans = timeout;
+  _res.retry = 4;
+  _res.nscount = config.nscount;
+  _res.options = RES_INIT | RES_RECURSE | RES_DEFNAMES | RES_DNSRCH;
+  _res.ndots = 1;
+  if (test_verbose)
+    {
+      printf ("info: new timeout value: %d\n", _res.retrans);
+      printf ("info: new retry attempt value: %d\n", _res.retry);
+      printf ("info: new _res.options: 0x%lx\n", _res.options);
+      printf ("info: new _res.nscount value: %d\n", _res.nscount);
+      printf ("info: new _res.ndots value: %d\n", _res.ndots);
+    }
+  for (int server_index = 0; server_index < config.nscount; ++server_index)
+    {
+      _res.nsaddr_list[server_index] = obj->servers[server_index].address;
+      if (test_verbose)
+        {
+          char buf[256];
+          TEST_VERIFY_EXIT
+            (inet_ntop (AF_INET, &obj->servers[server_index].address.sin_addr,
+                        buf, sizeof (buf)) != NULL);
+          printf ("info: server %d: %s/%u\n",
+                  server_index, buf,
+                  htons (obj->servers[server_index].address.sin_port));
+        }
+    }
+
+  set_search_path (config);
+
+  return obj;
+}
+
+void
+resolv_test_end (struct resolv_test *obj)
+{
+  res_close ();
+
+  xpthread_mutex_lock (&obj->lock);
+  obj->termination_requested = true;
+  xpthread_mutex_unlock (&obj->lock);
+
+  /* Send trigger packets to unblock the server threads.  */
+  for (int server_index = 0; server_index < obj->config.nscount;
+       ++server_index)
+    {
+      if (!obj->config.servers[server_index].disable_udp)
+        {
+          int sock = xsocket (AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+          xsendto (sock, "", 1, 0,
+                   (struct sockaddr *) &obj->servers[server_index].address,
+                   sizeof (obj->servers[server_index].address));
+          close (sock);
+        }
+      if (!obj->config.servers[server_index].disable_tcp)
+        {
+          int sock = xsocket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
+          xconnect (sock,
+                    (struct sockaddr *) &obj->servers[server_index].address,
+                    sizeof (obj->servers[server_index].address));
+          close (sock);
+        }
+    }
+
+  if (obj->config.single_thread_udp)
+    xpthread_join (obj->thread_udp_single);
+
+  /* Wait for the server threads to terminate.  */
+  for (int server_index = 0; server_index < obj->config.nscount;
+       ++server_index)
+    {
+      if (!obj->config.servers[server_index].disable_udp)
+        {
+          if (!obj->config.single_thread_udp)
+            xpthread_join (obj->servers[server_index].thread_udp);
+          close (obj->servers[server_index].socket_udp);
+        }
+      if (!obj->config.servers[server_index].disable_tcp)
+        {
+          xpthread_join (obj->servers[server_index].thread_tcp);
+          close (obj->servers[server_index].socket_tcp);
+        }
+    }
+
+  free (obj);
+}
diff --git a/support/resolv_test.h b/support/resolv_test.h
new file mode 100644
index 0000000..39dd625
--- /dev/null
+++ b/support/resolv_test.h
@@ -0,0 +1,169 @@ 
+/* DNS test framework and libresolv redirection.
+   Copyright (C) 2016 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 SUPPORT_RESOLV_TEST_H
+#define SUPPORT_RESOLV_TEST_H
+
+#include <arpa/nameser.h>
+#include <stdbool.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* This struct provides context information when the response callback
+   specified in struct resolv_redirect_config is invoked. */
+struct resolv_response_context
+{
+  const unsigned char *query_buffer;
+  size_t query_length;
+  int server_index;
+  bool tcp;
+};
+
+/* This opaque struct is used to construct responses from within the
+   response callback function.  */
+struct resolv_response_builder;
+
+/* This opaque struct collects information about the resolver testing
+   currently in progress.  */
+struct resolv_test;
+
+enum
+  {
+    /* Maximum number of test servers supported by the framework.  */
+    resolv_max_test_servers = 3,
+  };
+
+/* Configuration settings specific to individual test servers.  */
+struct resolv_redirect_server_config
+{
+  bool disable_tcp;             /* If true, no TCP server is listening.  */
+  bool disable_udp;             /* If true, no UDP server is listening.  */
+};
+
+/* Instructions for setting up the libresolv redirection.  */
+struct resolv_redirect_config
+{
+  /* The response_callback function is called for every incoming DNS
+     packet, over UDP or TCP.  It must be specified, the other
+     configuration settings are optional.  */
+  void (*response_callback) (const struct resolv_response_context *,
+                             struct resolv_response_builder *,
+                             const char *qname,
+                             uint16_t qclass, uint16_t qtype);
+
+  /* Per-server configuration.  */
+  struct resolv_redirect_server_config servers[resolv_max_test_servers];
+
+  /* Search path entries.  The first entry serves as the default
+     domain name as well.  */
+  const char *search[7];
+
+  /* Number of servers to activate in resolv.  0 means the default,
+     resolv_max_test_servers.  */
+  int nscount;
+
+  /* If true, use a single thread to process all UDP queries.  This
+     may results in more predictable ordering of queries and
+     responses.  */
+  bool single_thread_udp;
+};
+
+/* Configure NSS to use, nss_dns only for aplicable databases, and try
+   to put the process into a network namespace for better isolation.
+   This may have to be called before resolv_test_start, before the
+   process creates any threads.  Otherwise, initialization is
+   performed by resolv_test_start implicitly.  */
+void resolv_test_init (void);
+
+/* Initiate resolver testing.  This updates the _res variable as
+   needed.  As a side effect, NSS is reconfigured to use nss_dns only
+   for aplicable databases, and the process may enter a network
+   namespace for better isolation.  */
+struct resolv_test *resolv_test_start (struct resolv_redirect_config);
+
+/* Call this function at the end of resolver testing, to free
+   resources and report pending errors (if any).  */
+void resolv_test_end (struct resolv_test *);
+
+/* The remaining facilities in this file are used for constructing
+   response packets from the response_callback function.  */
+
+/* Special settings for constructing responses from the callback.  */
+struct resolv_response_flags
+{
+  /* 4-bit response code to incorporate into the response. */
+  unsigned char rcode;
+
+  /* If true, the TC (truncation) flag will be set.  */
+  bool tc;
+
+  /* Initial section count values.  Can be used to artificially
+     increase the counts, for malformed packet testing.*/
+  unsigned short qdcount;
+  unsigned short ancount;
+  unsigned short nscount;
+  unsigned short adcount;
+};
+
+/* Begin a new response with the requested flags.  Must be called
+   first.  */
+void resolv_response_init (struct resolv_response_builder *,
+                           struct resolv_response_flags);
+
+/* Switches to the section in the response packet.  Only forward
+   movement is supported.  */
+void resolv_response_section (struct resolv_response_builder *, ns_sect);
+
+/* Add a question record to the question section.  */
+void resolv_response_add_question (struct resolv_response_builder *,
+                                   const char *name, uint16_t class,
+                                   uint16_t type);
+/* Starts a new resource record with the specified owner name, class,
+   type, and TTL.  Data is supplied with resolv_response_add_data or
+   resolv_response_add_name.  */
+void resolv_response_open_record (struct resolv_response_builder *,
+                                  const char *name, uint16_t class,
+                                  uint16_t type, uint32_t ttl);
+
+/* Add unstructed bytes to the RDATA part of a resource record.  */
+void resolv_response_add_data (struct resolv_response_builder *,
+                               const void *, size_t);
+
+/* Add a compressed domain name to the RDATA part of a resource
+   record.  */
+void resolv_response_add_name (struct resolv_response_builder *,
+                               const char *name);
+
+/* Mark the end of the constructed record.  Must be called last.  */
+void resolv_response_close_record (struct resolv_response_builder *);
+
+/* Drop this query packet (that is, do not send a response, not even
+   an empty packet).  */
+void resolv_response_drop (struct resolv_response_builder *);
+
+/* In TCP mode, close the connection after this packet (if a response
+   is sent).  */
+void resolv_response_close (struct resolv_response_builder *);
+
+/* The size of the response packet built so far.  */
+size_t resolv_response_length (const struct resolv_response_builder *);
+
+__END_DECLS
+
+#endif /* SUPPORT_RESOLV_TEST_H */
diff --git a/support/run_diff.h b/support/run_diff.h
new file mode 100644
index 0000000..af340ef
--- /dev/null
+++ b/support/run_diff.h
@@ -0,0 +1,31 @@ 
+/* Invoke the system diff tool to compare two strings.
+   Copyright (C) 2016 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 SUPPORT_RUN_DIFF_H
+#define SUPPORT_RUN_DIFF_H
+
+/* Compare the two NUL-terminated strings LEFT and RIGHT using the
+   diff tool.  Label the sides of the diff with LEFT_LABEL and
+   RIGHT_LABEL, respectively.
+
+   This function assumes that LEFT and RIGHT are different
+   strings.  */
+void support_run_diff (const char *left_label, const char *left,
+                       const char *right_label, const char *right);
+
+#endif /* SUPPORT_RUN_DIFF_H */
diff --git a/support/support.h b/support/support.h
index fc7fba9..136e0ce 100644
--- a/support/support.h
+++ b/support/support.h
@@ -52,6 +52,7 @@  void *xcalloc (size_t n, size_t s) __attribute__ ((malloc));
 void *xrealloc (void *p, size_t n);
 char *xasprintf (const char *format, ...)
   __attribute__ ((format (printf, 1, 2), malloc));
+char *xstrdup (const char *);
 
 __END_DECLS
 
diff --git a/support/support_format_address_family.c b/support/support_format_address_family.c
new file mode 100644
index 0000000..06c6af7
--- /dev/null
+++ b/support/support_format_address_family.c
@@ -0,0 +1,35 @@ 
+/* Convert an address family to a string.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <support/support.h>
+
+char *
+support_format_address_family (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return xstrdup ("INET");
+    case AF_INET6:
+      return xstrdup ("INET6");
+    default:
+      return xasprintf ("<unknown address family %d>", family);
+    }
+}
diff --git a/support/support_format_addrinfo.c b/support/support_format_addrinfo.c
new file mode 100644
index 0000000..744aad4
--- /dev/null
+++ b/support/support_format_addrinfo.c
@@ -0,0 +1,202 @@ 
+/* Convert struct addrinfo values to a string.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <stdio.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+static size_t
+socket_address_length (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return sizeof (struct sockaddr_in);
+    case AF_INET6:
+      return sizeof (struct sockaddr_in6);
+    default:
+      return -1;
+    }
+}
+
+static void
+format_ai_flags (FILE *out, struct addrinfo *ai, int flag, const char *name,
+                 int * flags_printed)
+{
+  if ((ai->ai_flags & flag) != 0)
+    fprintf (out, " %s", name);
+  *flags_printed |= flag;
+}
+
+static void
+format_ai_one (FILE *out, struct addrinfo *ai, int *flags)
+{
+  /* ai_flags */
+  if (ai->ai_flags != *flags)
+    {
+      fprintf (out, "flags:");
+      int flags_printed = 0;
+#define FLAG(flag) format_ai_flags (out, ai, flag, #flag, &flags_printed)
+      FLAG (AI_PASSIVE);
+      FLAG (AI_CANONNAME);
+      FLAG (AI_NUMERICHOST);
+      FLAG (AI_V4MAPPED);
+      FLAG (AI_ALL);
+      FLAG (AI_ADDRCONFIG);
+      FLAG (AI_IDN);
+      FLAG (AI_CANONIDN);
+      FLAG (AI_IDN_ALLOW_UNASSIGNED);
+      FLAG (AI_IDN_USE_STD3_ASCII_RULES);
+      FLAG (AI_NUMERICSERV);
+#undef FLAG
+      int remaining = ai->ai_flags & ~flags_printed;
+      if (remaining != 0)
+        fprintf (out, " %08x", remaining);
+      fprintf (out, "\n");
+      *flags = ai->ai_flags;
+    }
+
+  {
+    char type_buf[32];
+    const char *type_str;
+    char proto_buf[32];
+    const char *proto_str;
+
+    /* ai_socktype */
+    switch (ai->ai_socktype)
+      {
+      case SOCK_RAW:
+        type_str = "RAW";
+        break;
+      case SOCK_DGRAM:
+        type_str = "DGRAM";
+        break;
+      case SOCK_STREAM:
+        type_str = "STREAM";
+        break;
+      default:
+        snprintf (type_buf, sizeof (type_buf), "%d", ai->ai_socktype);
+        type_str = type_buf;
+      }
+
+    /* ai_protocol */
+    switch (ai->ai_protocol)
+      {
+      case IPPROTO_IP:
+        proto_str = "IP";
+        break;
+      case IPPROTO_UDP:
+        proto_str = "UDP";
+        break;
+      case IPPROTO_TCP:
+        proto_str = "TCP";
+        break;
+      default:
+        snprintf (proto_buf, sizeof (proto_buf), "%d", ai->ai_protocol);
+        proto_str = proto_buf;
+      }
+    fprintf (out, "address: %s/%s", type_str, proto_str);
+  }
+
+  /* ai_addrlen */
+  if (ai->ai_addrlen != socket_address_length (ai->ai_family))
+    {
+      char *family = support_format_address_family (ai->ai_family);
+      fprintf (out, "error: invalid address length %d for %s\n",
+               ai->ai_addrlen, family);
+      free (family);
+    }
+
+  /* ai_addr */
+  {
+    char buf[128];
+    uint16_t port;
+    const char *ret;
+    switch (ai->ai_family)
+      {
+      case AF_INET:
+        {
+          struct sockaddr_in *sin = (struct sockaddr_in *) ai->ai_addr;
+          ret = inet_ntop (AF_INET, &sin->sin_addr, buf, sizeof (buf));
+          port = sin->sin_port;
+        }
+        break;
+      case AF_INET6:
+        {
+          struct sockaddr_in6 *sin = (struct sockaddr_in6 *) ai->ai_addr;
+          ret = inet_ntop (AF_INET6, &sin->sin6_addr, buf, sizeof (buf));
+          port = sin->sin6_port;
+        }
+        break;
+      default:
+        errno = EAFNOSUPPORT;
+        ret = NULL;
+      }
+    if (ret == NULL)
+        fprintf (out, "error: inet_top failed: %m\n");
+    else
+      fprintf (out, " %s %u\n", buf, ntohs (port));
+  }
+
+  /* ai_canonname */
+  if (ai->ai_canonname != NULL)
+    fprintf (out, "canonname: %s\n", ai->ai_canonname);
+}
+
+/* Format all the addresses in one address family.  */
+static void
+format_ai_family (FILE *out, struct addrinfo *ai, int family, int *flags)
+{
+  while (ai)
+    {
+      if (ai->ai_family == family)
+        format_ai_one (out, ai, flags);
+      ai = ai->ai_next;
+    }
+}
+
+char *
+support_format_addrinfo (struct addrinfo *ai, int ret)
+{
+  int errno_copy = errno;
+
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+  if (ret != 0)
+    {
+      fprintf (mem.out, "error: %s\n", gai_strerror (ret));
+      if (ret == EAI_SYSTEM)
+        {
+          errno = errno_copy;
+          fprintf (mem.out, "error: %m\n");
+        }
+    }
+  else
+    {
+      int flags = 0;
+      format_ai_family (mem.out, ai, AF_INET, &flags);
+      format_ai_family (mem.out, ai, AF_INET6, &flags);
+    }
+
+  xfclose_memstream (&mem);
+  return mem.buffer;
+}
diff --git a/support/support_format_dns_packet.c b/support/support_format_dns_packet.c
new file mode 100644
index 0000000..d073ce0
--- /dev/null
+++ b/support/support_format_dns_packet.c
@@ -0,0 +1,215 @@ 
+/* Convert a DNS packet to a human-readable representation.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <arpa/inet.h>
+#include <resolv.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+struct in_buffer
+{
+  const unsigned char *data;
+  size_t size;
+};
+
+static inline bool
+extract_8 (struct in_buffer *in, unsigned char *value)
+{
+  if (in->size == 0)
+    return false;
+  *value = in->data[0];
+  ++in->data;
+  --in->size;
+  return true;
+}
+
+static inline bool
+extract_16 (struct in_buffer *in, unsigned short *value)
+{
+  if (in->size < 2)
+    return false;
+  *value = (in->data[0] << 8) | in->data[1];
+  in->data += 2;
+  in->size -= 2;
+  return true;
+}
+
+static inline bool
+extract_32 (struct in_buffer *in, unsigned *value)
+{
+  if (in->size < 4)
+    return false;
+  unsigned a = in->data[0];
+  unsigned b = in->data[1];
+  unsigned c = in->data[2];
+  unsigned d = in->data[3];
+  *value = (a << 24) | (b << 16) | (c << 8) | d;
+  in->data += 4;
+  in->size -= 4;
+  return true;
+}
+
+static inline bool
+extract_bytes (struct in_buffer *in, size_t length, struct in_buffer *value)
+{
+  if (in->size < length)
+    return false;
+  *value = (struct in_buffer) {in->data, length};
+  in->data += length;
+  in->size -= length;
+  return true;
+}
+
+struct dname
+{
+  char name[MAXDNAME + 1];
+};
+
+static bool
+extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value)
+{
+  const unsigned char *full_end = full.data + full.size;
+  /* Sanity checks; these indicate buffer misuse.  */
+  TEST_VERIFY_EXIT
+    (!(in->data < full.data || in->data > full_end
+       || in->size > (size_t) (full_end - in->data)));
+  int ret = dn_expand (full.data, full_end, in->data,
+                       value->name, sizeof (value->name));
+  if (ret < 0)
+    return false;
+  in->data += ret;
+  in->size -= ret;
+  return true;
+}
+
+char *
+support_format_dns_packet (const unsigned char *buffer, size_t length)
+{
+  struct in_buffer full = { buffer, length };
+  struct in_buffer in = full;
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+
+  unsigned short txnid;
+  unsigned short flags;
+  unsigned short qdcount;
+  unsigned short ancount;
+  unsigned short nscount;
+  unsigned short adcount;
+  if (!(extract_16 (&in, &txnid)
+        && extract_16 (&in, &flags)
+        && extract_16 (&in, &qdcount)
+        && extract_16 (&in, &ancount)
+        && extract_16 (&in, &nscount)
+        && extract_16 (&in, &adcount)))
+    {
+      fprintf (mem.out, "error: could not parse DNS header\n");
+      goto out;
+    }
+  if (qdcount != 1)
+    {
+      fprintf (mem.out, "error: question count is %d, not 1\n", qdcount);
+      goto out;
+    }
+  struct dname qname;
+  if (!extract_name (full, &in, &qname))
+    {
+      fprintf (mem.out, "error: malformed QNAME\n");
+      goto out;
+    }
+  unsigned short qtype;
+  unsigned short qclass;
+  if (!(extract_16 (&in, &qtype)
+        && extract_16 (&in, &qclass)))
+    {
+      fprintf (mem.out, "error: malformed question\n");
+      goto out;
+    }
+  if (qtype != T_A && qtype != T_AAAA && qtype != T_PTR)
+    {
+      fprintf (mem.out, "error: unsupported QTYPE %d\n", qtype);
+      goto out;
+    }
+
+  fprintf (mem.out, "name: %s\n", qname.name);
+
+  for (int i = 0; i < ancount; ++i)
+    {
+      struct dname rname;
+      if (!extract_name (full, &in, &rname))
+        {
+          fprintf (mem.out, "error: malformed record name\n");
+          goto out;
+        }
+      unsigned short rtype;
+      unsigned short rclass;
+      unsigned ttl;
+      unsigned short rdlen;
+      struct in_buffer rdata;
+      if (!(extract_16 (&in, &rtype)
+            && extract_16 (&in, &rclass)
+            && extract_32 (&in, &ttl)
+            && extract_16 (&in, &rdlen)
+            && extract_bytes (&in, rdlen, &rdata)))
+        {
+          fprintf (mem.out, "error: malformed record header\n");
+          goto out;
+        }
+      /* Skip non-matching record types.  */
+      if (rtype != qtype || rclass != qclass)
+        continue;
+      switch (rtype)
+        {
+        case T_A:
+          if (rdlen == 4)
+              fprintf (mem.out, "address: %d.%d.%d.%d\n",
+                       rdata.data[0],
+                       rdata.data[1],
+                       rdata.data[2],
+                       rdata.data[3]);
+          else
+            fprintf (mem.out, "error: A record of size %d: %s\n", rdlen, rname.name);
+          break;
+        case T_AAAA:
+          {
+            char buf[100];
+            if (inet_ntop (AF_INET6, rdata.data, buf, sizeof (buf)) == NULL)
+              fprintf (mem.out, "error: AAAA record decoding failed: %m\n");
+            else
+              fprintf (mem.out, "address: %s\n", buf);
+          }
+          break;
+        case T_CNAME:
+        case T_PTR:
+          {
+            struct dname name;
+            if (extract_name (full, &in, &name))
+              fprintf (mem.out, "name: %s\n", name.name);
+            else
+              fprintf (mem.out, "error: malformed CNAME/PTR record\n");
+          }
+        }
+    }
+
+ out:
+  xfclose_memstream (&mem);
+  return mem.buffer;
+}
diff --git a/support/support_format_herrno.c b/support/support_format_herrno.c
new file mode 100644
index 0000000..42279f6
--- /dev/null
+++ b/support/support_format_herrno.c
@@ -0,0 +1,45 @@ 
+/* Convert a h_errno error code to a string.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <support/support.h>
+
+char *
+support_format_herrno (int code)
+{
+  const char *errstr;
+  switch (code)
+    {
+    case HOST_NOT_FOUND:
+      errstr = "HOST_NOT_FOUND";
+      break;
+    case NO_ADDRESS:
+      errstr = "NO_ADDRESS";
+      break;
+    case NO_RECOVERY:
+      errstr = "NO_RECOVERY";
+      break;
+    case TRY_AGAIN:
+      errstr = "TRY_AGAIN";
+      break;
+    default:
+      return xasprintf ("<invalid h_errno value %d>\n", code);
+    }
+  return xstrdup (errstr);
+}
diff --git a/support/support_format_hostent.c b/support/support_format_hostent.c
new file mode 100644
index 0000000..5622f69
--- /dev/null
+++ b/support/support_format_hostent.c
@@ -0,0 +1,75 @@ 
+/* Convert a struct hostent object to a string.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+static int
+address_length (int family)
+{
+  switch (family)
+    {
+    case AF_INET:
+      return 4;
+    case AF_INET6:
+      return 16;
+    }
+  return -1;
+}
+
+char *
+support_format_hostent (struct hostent *h)
+{
+  if (h == NULL)
+    {
+      char *value = support_format_herrno (h_errno);
+      char *result = xasprintf ("error: %s\n", value);
+      free (value);
+      return result;
+    }
+
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+
+  fprintf (mem.out, "name: %s\n", h->h_name);
+  for (char **alias = h->h_aliases; *alias != NULL; ++alias)
+    fprintf (mem.out, "alias: %s\n", *alias);
+  for (unsigned i = 0; h->h_addr_list[i] != NULL; ++i)
+    {
+      char buf[128];
+      if (inet_ntop (h->h_addrtype, h->h_addr_list[i],
+                     buf, sizeof (buf)) == NULL)
+        fprintf (mem.out, "error: inet_ntop failed: %m\n");
+      else
+        fprintf (mem.out, "address: %s\n", buf);
+    }
+  if (h->h_length != address_length (h->h_addrtype))
+    {
+      char *family = support_format_address_family (h->h_addrtype);
+      fprintf (mem.out, "error: invalid address length %d for %s\n",
+               h->h_length, family);
+      free (family);
+    }
+
+  xfclose_memstream (&mem);
+  return mem.buffer;
+}
diff --git a/support/support_format_netent.c b/support/support_format_netent.c
new file mode 100644
index 0000000..51a3a6b
--- /dev/null
+++ b/support/support_format_netent.c
@@ -0,0 +1,50 @@ 
+/* Convert a struct netent object to a string.
+   Copyright (C) 2016 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 <support/format_nss.h>
+
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <support/support.h>
+#include <support/xmemstream.h>
+
+char *
+support_format_netent (struct netent *e)
+{
+  if (e == NULL)
+    {
+      char *value = support_format_herrno (h_errno);
+      char *result = xasprintf ("error: %s\n", value);
+      free (value);
+      return result;
+    }
+
+  struct xmemstream mem;
+  xopen_memstream (&mem);
+
+  if (e->n_name != NULL)
+    fprintf (mem.out, "name: %s\n", e->n_name);
+  for (char **ap = e->n_aliases; *ap != NULL; ++ap)
+    fprintf (mem.out, "alias: %s\n", *ap);
+  if (e->n_addrtype != AF_INET)
+    fprintf (mem.out, "addrtype: %d\n", e->n_addrtype);
+  fprintf (mem.out, "net: 0x%08x\n", e->n_net);
+
+  xfclose_memstream (&mem);
+  return mem.buffer;
+}
diff --git a/support/support_run_diff.c b/support/support_run_diff.c
new file mode 100644
index 0000000..a963e01
--- /dev/null
+++ b/support/support_run_diff.c
@@ -0,0 +1,76 @@ 
+/* Invoke the system diff tool to compare two strings.
+   Copyright (C) 2016 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 <support/run_diff.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/temp_file.h>
+#include <sys/wait.h>
+#include <xunistd.h>
+
+static char *
+write_to_temp_file (const char *prefix, const char *str)
+{
+  char *template = xasprintf ("run_diff-%s", prefix);
+  char *name = NULL;
+  int fd = create_temp_file (template, &name);
+  TEST_VERIFY_EXIT (fd >= 0);
+  free (template);
+  xwrite (fd, str, strlen (str));
+  TEST_VERIFY_EXIT (close (fd) == 0);
+  return name;
+}
+
+void
+support_run_diff (const char *left_label, const char *left,
+                  const char *right_label, const char *right)
+{
+  /* Ensure that the diff command output is ordered properly with
+     standard output.  */
+  TEST_VERIFY_EXIT (fflush (stdout) == 0);
+
+  char *left_path = write_to_temp_file ("left-diff", left);
+  char *right_path = write_to_temp_file ("right-diff", right);
+
+  pid_t pid = xfork ();
+  if (pid == 0)
+    {
+      execlp ("diff", "diff", "-u",
+              "--label", left_label, "--label", right_label,
+              "--", left_path, right_path,
+              NULL);
+      _exit (17);
+    }
+  else
+    {
+      int status;
+      xwaitpid (pid, &status, 0);
+      if (!WIFEXITED (status) || WEXITSTATUS (status) != 1)
+        printf ("warning: could not run diff, exit status: %d\n"
+                "*** %s ***\n%s\n"
+                "*** %s ***\n%s\n",
+                status, left_label, left, right_label, right);
+    }
+
+  free (right_path);
+  free (left_path);
+}
diff --git a/support/xaccept.c b/support/xaccept.c
new file mode 100644
index 0000000..387fc9f
--- /dev/null
+++ b/support/xaccept.c
@@ -0,0 +1,32 @@ 
+/* accept with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+int
+xaccept (int fd, struct sockaddr *sa, socklen_t *salen)
+{
+  int clientfd = accept (fd, sa, salen);
+  if (clientfd < 0)
+    FAIL_EXIT1 ("accept (%d): %m", fd);
+  return clientfd;
+}
diff --git a/support/xbind.c b/support/xbind.c
new file mode 100644
index 0000000..59e0c3a
--- /dev/null
+++ b/support/xbind.c
@@ -0,0 +1,30 @@ 
+/* bind with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xbind (int fd, const struct sockaddr *sa, socklen_t sa_len)
+{
+  if (bind (fd, sa, sa_len) != 0)
+    FAIL_EXIT1 ("bind (%d), family %d: %m", fd, sa->sa_family);
+}
diff --git a/support/xconnect.c b/support/xconnect.c
new file mode 100644
index 0000000..b4fe94c
--- /dev/null
+++ b/support/xconnect.c
@@ -0,0 +1,30 @@ 
+/* connect with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xconnect (int fd, const struct sockaddr *sa, socklen_t sa_len)
+{
+  if (connect (fd, sa, sa_len) != 0)
+    FAIL_EXIT1 ("connect (%d), family %d: %m", fd, sa->sa_family);
+}
diff --git a/support/xfclose.c b/support/xfclose.c
new file mode 100644
index 0000000..45ca08c
--- /dev/null
+++ b/support/xfclose.c
@@ -0,0 +1,33 @@ 
+/* fclose with error checking.
+   Copyright (C) 2016 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 <support/xstdio.h>
+
+#include <support/check.h>
+#include <stdlib.h>
+
+void
+xfclose (FILE *fp)
+{
+  if (ferror (fp))
+    FAIL_EXIT1 ("stdio stream closed with pending errors");
+  if (fflush (fp) != 0)
+    FAIL_EXIT1 ("fflush: %m");
+  if (fclose (fp) != 0)
+    FAIL_EXIT1 ("fclose: %m");
+}
diff --git a/support/xfopen.c b/support/xfopen.c
new file mode 100644
index 0000000..436ab55
--- /dev/null
+++ b/support/xfopen.c
@@ -0,0 +1,31 @@ 
+/* fopen with error checking.
+   Copyright (C) 2016 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 <support/xstdio.h>
+
+#include <support/check.h>
+#include <stdlib.h>
+
+FILE *
+xfopen (const char *path, const char *mode)
+{
+  FILE *fp = fopen (path, mode);
+  if (fp == NULL)
+    FAIL_EXIT1 ("could not open %s (mode \"%s\"): %m", path, mode);
+  return fp;
+}
diff --git a/support/xgetsockname.c b/support/xgetsockname.c
new file mode 100644
index 0000000..c66981e
--- /dev/null
+++ b/support/xgetsockname.c
@@ -0,0 +1,30 @@ 
+/* getsockname with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xgetsockname (int fd, struct sockaddr *sa, socklen_t *plen)
+{
+  if (getsockname (fd, sa, plen) != 0)
+    FAIL_EXIT1 ("setsockopt (%d): %m", fd);
+}
diff --git a/support/xlisten.c b/support/xlisten.c
new file mode 100644
index 0000000..e099e5b
--- /dev/null
+++ b/support/xlisten.c
@@ -0,0 +1,30 @@ 
+/* listen with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xlisten (int fd, int backlog)
+{
+  if (listen (fd, backlog) != 0)
+    FAIL_EXIT1 ("listen (%d, %d): %m", fd, backlog);
+}
diff --git a/support/xmemstream.c b/support/xmemstream.c
new file mode 100644
index 0000000..697d7c7
--- /dev/null
+++ b/support/xmemstream.c
@@ -0,0 +1,42 @@ 
+/* Error-checking wrappers for memstream functions.
+   Copyright (C) 2016 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 <support/xmemstream.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/xstdio.h>
+
+void
+xopen_memstream (struct xmemstream *stream)
+{
+  int old_errno = errno;
+  *stream = (struct xmemstream) {};
+  stream->out = open_memstream (&stream->buffer, &stream->length);
+  if (stream->out == NULL)
+    FAIL_EXIT1 ("open_memstream: %m");
+  errno = old_errno;
+}
+
+void
+xfclose_memstream (struct xmemstream *stream)
+{
+  xfclose (stream->out);
+  stream->out = NULL;
+}
diff --git a/support/xmemstream.h b/support/xmemstream.h
new file mode 100644
index 0000000..bb3394a
--- /dev/null
+++ b/support/xmemstream.h
@@ -0,0 +1,49 @@ 
+/* Error-checking wrappers for memstream functions.
+   Copyright (C) 2016 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 SUPPORT_XMEMSTREAM_H
+#define SUPPORT_XMEMSTREAM_H
+
+#include <stdio.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/* Wrappers for other libc functions.  */
+struct xmemstream
+{
+  FILE *out;
+  char *buffer;
+  size_t length;
+};
+
+/* Create a new in-memory stream.  Initializes *STREAM.  After this
+   function returns, STREAM->out is a file descriptor open for
+   writing.  errno is preserved, so that the %m format specifier can
+   be used for writing to STREAM->out.  */
+void xopen_memstream (struct xmemstream *stream);
+
+/* Closes STREAM->OUT.  After this function returns, STREAM->buffer
+   and STREAM->length denote a memory range which contains the bytes
+   written to the output stream.  The caller should free
+   STREAM->buffer.  */
+void xfclose_memstream (struct xmemstream *stream);
+
+__END_DECLS
+
+#endif /* SUPPORT_XMEMSTREAM_H */
diff --git a/support/xpoll.c b/support/xpoll.c
new file mode 100644
index 0000000..3aaecde
--- /dev/null
+++ b/support/xpoll.c
@@ -0,0 +1,32 @@ 
+/* poll with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+int
+xpoll (struct pollfd *fds, nfds_t nfds, int timeout)
+{
+  int ret = poll (fds, nfds, timeout);
+  if (ret < 0)
+    FAIL_EXIT1 ("poll: %m");
+  return ret;
+}
diff --git a/support/xpthread_once.c b/support/xpthread_once.c
new file mode 100644
index 0000000..6ba0c69
--- /dev/null
+++ b/support/xpthread_once.c
@@ -0,0 +1,25 @@ 
+/* pthread_once with error checking.
+   Copyright (C) 2016 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 <support/xthread.h>
+
+void
+xpthread_once (pthread_once_t *guard, void (*func) (void))
+{
+  xpthread_check_return ("pthread_once", pthread_once (guard, func));
+}
diff --git a/support/xrecvfrom.c b/support/xrecvfrom.c
new file mode 100644
index 0000000..2cd473a
--- /dev/null
+++ b/support/xrecvfrom.c
@@ -0,0 +1,33 @@ 
+/* recvfrom with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+size_t
+xrecvfrom (int fd, void *buf, size_t buflen, int flags,
+           struct sockaddr *sa, socklen_t *salen)
+{
+  ssize_t ret = recvfrom (fd, buf, buflen, flags, sa, salen);
+  if (ret < 0)
+    FAIL_EXIT1 ("error: recvfrom (%d), %zu bytes buffer: %m", fd, buflen);
+  return ret;
+}
diff --git a/support/xsendto.c b/support/xsendto.c
new file mode 100644
index 0000000..6963aa7
--- /dev/null
+++ b/support/xsendto.c
@@ -0,0 +1,35 @@ 
+/* sendto with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xsendto (int fd, const void *buf, size_t buflen, int flags,
+         const struct sockaddr *sa, socklen_t salen)
+{
+  ssize_t ret = sendto (fd, buf, buflen, flags, sa, salen);
+  if (ret < 0)
+    FAIL_EXIT1 ("sendto (%d), %zu bytes, family %d: %m",
+                fd, buflen, sa->sa_family);
+  if (ret != buflen)
+    FAIL_EXIT1 ("sendto (%d) sent %zd bytes instead of %zu", fd, ret, buflen);
+}
diff --git a/support/xsetsockopt.c b/support/xsetsockopt.c
new file mode 100644
index 0000000..8996554
--- /dev/null
+++ b/support/xsetsockopt.c
@@ -0,0 +1,31 @@ 
+/* setsockopt with error checking.
+   Copyright (C) 2016 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 <support/xsocket.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+
+void
+xsetsockopt (int fd, int level, int name, const void *val, socklen_t vallen)
+{
+  if (setsockopt (fd, level, name, val, vallen) != 0)
+    FAIL_EXIT1 ("setsockopt (%d, %d, %d), %zu bytes: %m",
+                fd, level, name, (size_t) vallen);
+}
diff --git a/support/xsocket.h b/support/xsocket.h
index e9ff49e..c376e09 100644
--- a/support/xsocket.h
+++ b/support/xsocket.h
@@ -19,9 +19,20 @@ 
 #ifndef SUPPORT_XSOCKET_H
 #define SUPPORT_XSOCKET_H
 
+#include <poll.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 
 int xsocket (int, int, int);
+void xsetsockopt (int, int, int, const void *, socklen_t);
+void xgetsockname (int, struct sockaddr *, socklen_t *);
+void xconnect (int, const struct sockaddr *, socklen_t);
+void xbind (int, const struct sockaddr *, socklen_t);
+void xlisten (int, int);
+int xaccept (int, struct sockaddr *, socklen_t *);
+void xsendto (int, const void *, size_t, int,
+              const struct sockaddr *, socklen_t);
+size_t xrecvfrom (int, void *, size_t, int, struct sockaddr *, socklen_t *);
+int xpoll (struct pollfd *, nfds_t, int);
 
 #endif /* SUPPORT_XSOCKET_H */
diff --git a/support/xstdio.h b/support/xstdio.h
new file mode 100644
index 0000000..99601ba
--- /dev/null
+++ b/support/xstdio.h
@@ -0,0 +1,32 @@ 
+/* Error-checking wrappers for stdio functions.
+   Copyright (C) 2016 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 SUPPORT_XSTDIO_H
+#define SUPPORT_XSTDIO_H
+
+#include <stdio.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+FILE *xfopen (const char *path, const char *mode);
+void xfclose (FILE *);
+
+__END_DECLS
+
+#endif /* SUPPORT_XSTDIO_H */
diff --git a/support/xstrdup.c b/support/xstrdup.c
new file mode 100644
index 0000000..62650fa
--- /dev/null
+++ b/support/xstrdup.c
@@ -0,0 +1,30 @@ 
+/* strdup with error checking.
+   Copyright (C) 2016 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 <support/support.h>
+
+#include <string.h>
+
+char *
+xstrdup (const char *s)
+{
+  char *p = strdup (s);
+  if (p == NULL)
+    oom_error ("strdup", strlen (s));
+  return p;
+}
diff --git a/support/xthread.h b/support/xthread.h
index f0dc0fa..375d882 100644
--- a/support/xthread.h
+++ b/support/xthread.h
@@ -51,6 +51,7 @@  pthread_t xpthread_create (pthread_attr_t *attr,
 void xpthread_detach (pthread_t thr);
 void xpthread_cancel (pthread_t thr);
 void *xpthread_join (pthread_t thr);
+void xpthread_once (pthread_once_t *guard, void (*func) (void));
 
 /* This function returns non-zero if pthread_barrier_wait returned
    PTHREAD_BARRIER_SERIAL_THREAD.  */
diff --git a/support/xunistd.h b/support/xunistd.h
index f0c7419..0fe532a8 100644
--- a/support/xunistd.h
+++ b/support/xunistd.h
@@ -30,6 +30,9 @@  __BEGIN_DECLS
 pid_t xfork (void);
 pid_t xwaitpid (pid_t, int *status, int flags);
 
+/* Write the buffer.  Retry on short writes.  */
+void xwrite (int, const void *, size_t);
+
 __END_DECLS
 
 #endif /* SUPPORT_XUNISTD_H */
diff --git a/support/xwrite.c b/support/xwrite.c
new file mode 100644
index 0000000..452359e
--- /dev/null
+++ b/support/xwrite.c
@@ -0,0 +1,39 @@ 
+/* write with error checking and retries.
+   Copyright (C) 2016 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 <support/xunistd.h>
+
+#include <support/check.h>
+
+void
+xwrite (int fd, const void *buffer, size_t length)
+{
+  const char *p = buffer;
+  const char *end = p + length;
+  while (p < end)
+    {
+      ssize_t ret = write (fd, p, end - p);
+      if (ret < 0)
+        FAIL_EXIT1 ("write of %zu bytes failed after %zd: %m",
+                    length, p - (const char *) buffer);
+      if (ret == 0)
+        FAIL_EXIT1 ("write return 0 after writing %zd bytes of %zu",
+                    p - (const char *) buffer, length);
+      p += ret;
+    }
+}