diff mbox

resolv: Mirror the entire resolver configuration in struct resolv_conf

Message ID 20170630193802.80D6C439942F0@oldenburg.str.redhat.com
State New
Headers show

Commit Message

Florian Weimer June 30, 2017, 7:38 p.m. UTC
This commit adds the remaining unchanging members (which are loaded
from /etc/resolv.conf) to struct resolv_conf.

The extended name server list is currently not used by the stub
resolver.  The switch depends on a cleanup: The _u._ext.nssocks
array stores just a single socket, and needs to be replaced with
a single socket value.

(The compatibility gethostname implementation does not use the
extended addres sort list, either.  Updating the compat code is
not worthwhile.)

2017-06-30  Florian Weimer  <fweimer@redhat.com>

	Mirror the entire resolver configuration in struct resolv_conf.
	* resolv/resolv_context.h (__resolv_context_nameserver_count)
	(__resolv_context_nameserver): New functions.
	(__resolv_context_sort_count, __resolv_context_sort_entry):
	Likewise.
	* resolv/nss_dns/dns-host.c (getanswer_r): Add struct
	resolv_context parameter.
	(gethostbyname3_context, _nss_dns_gethostbyaddr2_r): Adjust.
	(addrsort): Switch to struct resolv_context.
	* resolv/resolv_conf.h (struct resolv_sortlist_entry): Define.
	(struct resolv_conf): Add nameserver_list, nameserver_list_size,
	sort_list, sort_list_size, options, retrans, retry, ndots members.
	* resolv/resolv_conf.c (same_address_v4, same_address_v6)
	(same_address): New functions.
	(resolv_conf_matches): Compare the new array members.
	(__resolv_conf_allocate): Allocate and copy the new array members.
	(update_from_conf): Copy the entire configuration.
	* resolv/res_init.c (struct nameserver_list, struct sort_list):
	Define using dynarray.
	(struct resolv_conf_parser): Add nameserver_list, sort_list,
	template members.
	(resolv_conf_parser_init): Add preinit argument.  Initialize the
	new members.
	(resolv_conf_parser_free): Deallocate the new arrays.
	(allocate_address_v4): New function.
	(res_setoptions): Switch to struct resolv_conf_parser * parameter.
	(res_vinit_1): Drop res_state parameter.  Write all parsed date to
	the parser object instead.  Use allocate_address_v4 to allocate
	IPv4 addresses.
	(__res_vinit): Adjust.
	* resolv/tst-resolv-res_init-skeleton.c (print_resp): Print the
	extended name server list.
	(test_cases): Adjust.

Comments

Andreas Schwab July 4, 2017, 7:27 a.m. UTC | #1
This is breaking rpm:

https://build.opensuse.org/package/live_build_log/home:Andreas_Schwab:glibc/glibc/f/x86_64

rpm: resolv_conf.c:550: update_from_conf: Assertion `resolv_conf_matches (resp, conf)' failed.

Andreas.
Florian Weimer July 4, 2017, 8 a.m. UTC | #2
On 07/04/2017 09:27 AM, Andreas Schwab wrote:
> This is breaking rpm:
> 
> https://build.opensuse.org/package/live_build_log/home:Andreas_Schwab:glibc/glibc/f/x86_64
> 
> rpm: resolv_conf.c:550: update_from_conf: Assertion `resolv_conf_matches (resp, conf)' failed.

This has to be a property of the build root.  What's the contents of
/etc/resolv.conf?  What's the host name?

Thanks,
Florian
Andreas Schwab July 4, 2017, 8:08 a.m. UTC | #3
On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:

> This has to be a property of the build root.  What's the contents of
> /etc/resolv.conf?

There is none.

>  What's the host name?

Whatever the name of the build worker.

Andreas.
Florian Weimer July 4, 2017, 9:34 a.m. UTC | #4
On 07/04/2017 10:08 AM, Andreas Schwab wrote:
> On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:
> 
>> This has to be a property of the build root.  What's the contents of
>> /etc/resolv.conf?
> 
> There is none.

The test suite covers this, and I don't see any failures.  I don't see
it in manual testing, either.

>>  What's the host name?
> 
> Whatever the name of the build worker.

Sorry, I cannot reproduce this.

Do you have any custom patches for resolv.conf handling?  Could you post
a link to your source repository?

(I've just fixed another resolv.conf handling problem, but it's
unrelated to your problem, I assume.)

Thanks,
Florian
Andreas Schwab July 4, 2017, 10 a.m. UTC | #5
On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:

> Do you have any custom patches for resolv.conf handling?  

No.

> Could you post a link to your source repository?

Follow the links at the top.

Andreas.
Florian Weimer July 4, 2017, 10:09 a.m. UTC | #6
On 07/04/2017 12:00 PM, Andreas Schwab wrote:
> On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:
> 
>> Do you have any custom patches for resolv.conf handling?  
> 
> No.
> 
>> Could you post a link to your source repository?
> 
> Follow the links at the top.

I meant a link to something like the SRPM or the source repository from
which the SRPM is built.

I think the only way in which I could help is to provide a custom
logging patch which you could install temporarily during the build, and
we'll then see where it fails.  Would that work for you?

Thanks,
Florian
Andreas Schwab July 4, 2017, 10:14 a.m. UTC | #7
On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:

> I meant a link to something like the SRPM or the source repository from
> which the SRPM is built.

Yes, that's exactly linked from the top (Overview etc.).

> I think the only way in which I could help is to provide a custom
> logging patch which you could install temporarily during the build, and
> we'll then see where it fails.  Would that work for you?

Sure.  You can also build it yourself locally (you need an OBS account
for that though).

Andreas.
Florian Weimer July 4, 2017, 10:42 a.m. UTC | #8
On 07/04/2017 12:14 PM, Andreas Schwab wrote:
> On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:
> 
>> I meant a link to something like the SRPM or the source repository from
>> which the SRPM is built.
> 
> Yes, that's exactly linked from the top (Overview etc.).

If I do that, I end up at

http://software.opensuse.org//download.html?project=home%3AAndreas_Schwab%3Aglibc&package=glibc

which offers only glibc-2.22-118.1.src.rpm as the newest version.

>> I think the only way in which I could help is to provide a custom
>> logging patch which you could install temporarily during the build, and
>> we'll then see where it fails.  Would that work for you?
> 
> Sure.  You can also build it yourself locally (you need an OBS account
> for that though).

I'll send you a patch instead.  I suspect it might come in handy in
other cases, too.

Florian
Andreas Schwab July 4, 2017, 11:40 a.m. UTC | #9
On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:

> On 07/04/2017 12:14 PM, Andreas Schwab wrote:
>> On Jul 04 2017, Florian Weimer <fweimer@redhat.com> wrote:
>> 
>>> I meant a link to something like the SRPM or the source repository from
>>> which the SRPM is built.
>> 
>> Yes, that's exactly linked from the top (Overview etc.).
>
> If I do that, I end up at
>
> http://software.opensuse.org//download.html?project=home%3AAndreas_Schwab%3Aglibc&package=glibc
>
> which offers only glibc-2.22-118.1.src.rpm as the newest version.

Since the build failed no srpm was created, and publishing is disabled
anyway.  If you have an OBS account you can check out the sources via
command line.

Andreas.
Andreas Schwab July 4, 2017, 11:59 a.m. UTC | #10
(gdb) p *resp
$1 = {retrans = 5, retry = 2, options = 705, nscount = 1, nsaddr_list = {{
      sin_family = 2, sin_port = 13568, sin_addr = {s_addr = 16777343}, 
      sin_zero = "\272\272\272\272\272\272\272\272"}, {sin_family = 0, 
      sin_port = 0, sin_addr = {s_addr = 0}, 
      sin_zero = "\000\000\000\000\000\000\000"}, {sin_family = 0, 
      sin_port = 0, sin_addr = {s_addr = 0}, 
      sin_zero = "\000\000\000\000\000\000\000"}}, id = 0, dnsrch = {0x0, 0x0, 
    0x0, 0x0, 0x0, 0x0, 0x0}, defdname = '\000' <repeats 255 times>, 
  pfcode = 0, ndots = 1, nsort = 0, ipv6_unavail = 0, unused = 0, sort_list = {
    {addr = {s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
        s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
        s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
        s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
        s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}}, 
  __glibc_unused_qhook = 0x0, __glibc_unused_rhook = 0x0, res_h_errno = 0, 
  _vcsock = -1, _flags = 0, _u = {
    pad = "\000\000\000\000\000\000\000\000\377\377\377\377", '\000' <repeats 39 times>, _ext = {nscount = 0, nsmap = {0, 0, 0}, nssocks = {-1, 0, 0}, 
      nscount6 = 0, nsinit = 0, nsaddrs = {0x0, 0x0, 0x0}, 
      __glibc_extension_index = 0}}}
(gdb) p *conf
$2 = {__refcount = 3, nameserver_list = 0x606f38, nameserver_list_size = 1, 
  search_list = 0x606f40, search_list_size = 0, sort_list = 0x606f50, 
  sort_list_size = 0, options = 705, retrans = 5, retry = 2, ndots = 1}
(gdb) p conf.nameserver_list[0]
$3 = (const struct sockaddr *) 0x606f40
(gdb) p *$
$4 = {sa_family = 2, 
  sa_data = "\000\065\177\000\000\001\272\272\272\272\272\272\272\272"}

Andreas.
Florian Weimer July 4, 2017, 12:12 p.m. UTC | #11
On 07/04/2017 01:59 PM, Andreas Schwab wrote:
> (gdb) p *resp
> $1 = {retrans = 5, retry = 2, options = 705, nscount = 1, nsaddr_list = {{
>       sin_family = 2, sin_port = 13568, sin_addr = {s_addr = 16777343}, 
>       sin_zero = "\272\272\272\272\272\272\272\272"},

Seem this is [127.0.0.1]:53.

 {sin_family = 0,
>       sin_port = 0, sin_addr = {s_addr = 0}, 
>       sin_zero = "\000\000\000\000\000\000\000"}, {sin_family = 0, 
>       sin_port = 0, sin_addr = {s_addr = 0}, 
>       sin_zero = "\000\000\000\000\000\000\000"}}, id = 0, dnsrch = {0x0, 0x0, 
>     0x0, 0x0, 0x0, 0x0, 0x0}, defdname = '\000' <repeats 255 times>, 
>   pfcode = 0, ndots = 1, nsort = 0, ipv6_unavail = 0, unused = 0, sort_list = {
>     {addr = {s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
>         s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
>         s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
>         s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}, {addr = {
>         s_addr = 0}, mask = 0}, {addr = {s_addr = 0}, mask = 0}}, 
>   __glibc_unused_qhook = 0x0, __glibc_unused_rhook = 0x0, res_h_errno = 0, 
>   _vcsock = -1, _flags = 0, _u = {
>     pad = "\000\000\000\000\000\000\000\000\377\377\377\377", '\000' <repeats 39 times>, _ext = {nscount = 0, nsmap = {0, 0, 0}, nssocks = {-1, 0, 0}, 
>       nscount6 = 0, nsinit = 0, nsaddrs = {0x0, 0x0, 0x0}, 
>       __glibc_extension_index = 0}}}
> (gdb) p *conf
> $2 = {__refcount = 3, nameserver_list = 0x606f38, nameserver_list_size = 1, 
>   search_list = 0x606f40, search_list_size = 0, sort_list = 0x606f50, 
>   sort_list_size = 0, options = 705, retrans = 5, retry = 2, ndots = 1}
> (gdb) p conf.nameserver_list[0]
> $3 = (const struct sockaddr *) 0x606f40
> (gdb) p *$
> $4 = {sa_family = 2, 
>   sa_data = "\000\065\177\000\000\001\272\272\272\272\272\272\272\272"}

That looks like [127.0.0.1]:53, too.

So I don't see the mismatch.  Let's see what the additional logging will
reveal.  Sorry about this mess, but I don't see the bug so far.

Thanks,
Florian
Andreas Schwab July 4, 2017, 12:16 p.m. UTC | #12
298         if (!(resp->dnsrch[0] == resp->defdname
299               && resp->dnsrch[MAXDNSRCH] == NULL))

Andreas.
Florian Weimer July 4, 2017, 12:21 p.m. UTC | #13
On 07/04/2017 02:16 PM, Andreas Schwab wrote:
> 298         if (!(resp->dnsrch[0] == resp->defdname
> 299               && resp->dnsrch[MAXDNSRCH] == NULL))

Ah.  I assume this breaks for defname[0] == '\0' and dnsrch[0] == NULL.

It seems that this is triggered by having a host name which does not
contain a dot.  I'll fix this and add a test case.  Thanks.

Florian
Florian Weimer July 4, 2017, 12:55 p.m. UTC | #14
On 07/04/2017 02:21 PM, Florian Weimer wrote:
> On 07/04/2017 02:16 PM, Andreas Schwab wrote:
>> 298         if (!(resp->dnsrch[0] == resp->defdname
>> 299               && resp->dnsrch[MAXDNSRCH] == NULL))
> 
> Ah.  I assume this breaks for defname[0] == '\0' and dnsrch[0] == NULL.
> 
> It seems that this is triggered by having a host name which does not
> contain a dot.  I'll fix this and add a test case.  Thanks.

And this is the fix I'm going to commit.

Thanks,
Florian
diff mbox

Patch

diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 9d7ceb1..7cd54ab 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -108,7 +108,8 @@  typedef union querybuf
   u_char buf[MAXPACKET];
 } querybuf;
 
-static enum nss_status getanswer_r (const querybuf *answer, int anslen,
+static enum nss_status getanswer_r (struct resolv_context *ctx,
+				    const querybuf *answer, int anslen,
 				    const char *qname, int qtype,
 				    struct hostent *result, char *buffer,
 				    size_t buflen, int *errnop, int *h_errnop,
@@ -264,8 +265,9 @@  gethostbyname3_context (struct resolv_context *ctx,
       result->h_length = INADDRSZ;
     }
 
-  status = getanswer_r (host_buffer.buf, n, name, type, result, buffer, buflen,
-			errnop, h_errnop, map, ttlp, canonp);
+  status = getanswer_r
+    (ctx, host_buffer.buf, n, name, type, result, buffer, buflen,
+     errnop, h_errnop, map, ttlp, canonp);
   if (host_buffer.buf != orig_host_buffer)
     free (host_buffer.buf);
   return status;
@@ -522,8 +524,9 @@  _nss_dns_gethostbyaddr2_r (const void *addr, socklen_t len, int af,
       return errno == ECONNREFUSED ? NSS_STATUS_UNAVAIL : NSS_STATUS_NOTFOUND;
     }
 
-  status = getanswer_r (host_buffer.buf, n, qbuf, T_PTR, result, buffer, buflen,
-			errnop, h_errnop, 0 /* XXX */, ttlp, NULL);
+  status = getanswer_r
+    (ctx, host_buffer.buf, n, qbuf, T_PTR, result, buffer, buflen,
+     errnop, h_errnop, 0 /* XXX */, ttlp, NULL);
   if (host_buffer.buf != orig_host_buffer)
     free (host_buffer.buf);
   if (status != NSS_STATUS_SUCCESS)
@@ -553,25 +556,27 @@  _nss_dns_gethostbyaddr_r (const void *addr, socklen_t len, int af,
 				    errnop, h_errnop, NULL);
 }
 
-static void addrsort (char **ap, int num);
-
 static void
-addrsort (char **ap, int num)
+addrsort (struct resolv_context *ctx, char **ap, int num)
 {
   int i, j;
   char **p;
   short aval[MAX_NR_ADDRS];
   int needsort = 0;
+  size_t nsort = __resolv_context_sort_count (ctx);
 
   p = ap;
   if (num > MAX_NR_ADDRS)
     num = MAX_NR_ADDRS;
   for (i = 0; i < num; i++, p++)
     {
-      for (j = 0 ; (unsigned)j < _res.nsort; j++)
-	if (_res.sort_list[j].addr.s_addr ==
-	    (((struct in_addr *)(*p))->s_addr & _res.sort_list[j].mask))
-	  break;
+      for (j = 0 ; (unsigned)j < nsort; j++)
+	{
+	  struct resolv_sortlist_entry e
+	    = __resolv_context_sort_entry (ctx, j);
+	  if (e.addr.s_addr == (((struct in_addr *)(*p))->s_addr & e.mask))
+	    break;
+	}
       aval[i] = j;
       if (needsort == 0 && i > 0 && j < aval[i-1])
 	needsort = i;
@@ -598,7 +603,8 @@  addrsort (char **ap, int num)
 }
 
 static enum nss_status
-getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
+getanswer_r (struct resolv_context *ctx,
+	     const querybuf *answer, int anslen, const char *qname, int qtype,
 	     struct hostent *result, char *buffer, size_t buflen,
 	     int *errnop, int *h_errnop, int map, int32_t *ttlp, char **canonp)
 {
@@ -961,8 +967,9 @@  getanswer_r (const querybuf *answer, int anslen, const char *qname, int qtype,
        * in its return structures - should give it the "best"
        * address in that case, not some random one
        */
-      if (_res.nsort && haveanswer > 1 && qtype == T_A)
-	addrsort (host_data->h_addr_ptrs, haveanswer);
+      if (haveanswer > 1 && qtype == T_A
+	  && __resolv_context_sort_count (ctx) > 0)
+	addrsort (ctx, host_data->h_addr_ptrs, haveanswer);
 
       if (result->h_name == NULL)
 	{
diff --git a/resolv/res_init.c b/resolv/res_init.c
index 5941d37..80a21fb 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -104,7 +104,6 @@ 
 #include <errno.h>
 #include <resolv_conf.h>
 
-static void res_setoptions (res_state, const char *);
 static uint32_t net_mask (struct in_addr);
 
 unsigned long long int __res_initstamp;
@@ -124,28 +123,70 @@  is_sort_mask (char ch)
   return ch == '/' || ch == '&';
 }
 
+/* Array of name server addresses.  */
+#define DYNARRAY_STRUCT nameserver_list
+#define DYNARRAY_ELEMENT const struct sockaddr *
+#define DYNARRAY_ELEMENT_FREE(e) free ((struct sockaddr *) *(e))
+#define DYNARRAY_INITIAL_SIZE 3
+#define DYNARRAY_PREFIX nameserver_list_
+#include <malloc/dynarray-skeleton.c>
+
 /* Array of strings for the search array.  The backing store is
    managed separately.  */
 #define DYNARRAY_STRUCT search_list
 #define DYNARRAY_ELEMENT const char *
-#define DYNARRAY_INITIAL_SIZE 4
+#define DYNARRAY_INITIAL_SIZE 6
 #define DYNARRAY_PREFIX search_list_
 #include <malloc/dynarray-skeleton.c>
 
+/* Array of name server addresses.  */
+#define DYNARRAY_STRUCT sort_list
+#define DYNARRAY_ELEMENT struct resolv_sortlist_entry
+#define DYNARRAY_INITIAL_SIZE 0
+#define DYNARRAY_PREFIX sort_list_
+#include <malloc/dynarray-skeleton.c>
+
 /* resolv.conf parser state and results.  */
 struct resolv_conf_parser
 {
   char *buffer;            /* Temporary buffer for reading lines.  */
+
+  struct nameserver_list nameserver_list; /* Nameserver addresses.  */
+
   char *search_list_store; /* Backing storage for search list entries.  */
   struct search_list search_list; /* Points into search_list_store.  */
+
+  struct sort_list sort_list;   /* Address preference sorting list.  */
+
+  /* Configuration template.  The non-array elements are filled in
+     directly.  The array elements are updated prior to the call to
+     __resolv_conf_attach.  */
+  struct resolv_conf template;
 };
 
 static void
-resolv_conf_parser_init (struct resolv_conf_parser *parser)
+resolv_conf_parser_init (struct resolv_conf_parser *parser,
+                         const struct __res_state *preinit)
 {
   parser->buffer = NULL;
   parser->search_list_store = NULL;
+  nameserver_list_init (&parser->nameserver_list);
   search_list_init (&parser->search_list);
+  sort_list_init (&parser->sort_list);
+
+  if (preinit != NULL)
+    {
+      parser->template.retrans = preinit->retrans;
+      parser->template.retry = preinit->retry;
+      parser->template.options = preinit->options | RES_INIT;
+    }
+  else
+    {
+      parser->template.retrans = RES_TIMEOUT;
+      parser->template.retry = RES_DFLRETRY;
+      parser->template.options = RES_DEFAULT | RES_INIT;
+    }
+  parser->template.ndots = 1;
 }
 
 static void
@@ -153,7 +194,23 @@  resolv_conf_parser_free (struct resolv_conf_parser *parser)
 {
   free (parser->buffer);
   free (parser->search_list_store);
+  nameserver_list_free (&parser->nameserver_list);
   search_list_free (&parser->search_list);
+  sort_list_free (&parser->sort_list);
+}
+
+/* Allocate a struct sockaddr_in object on the heap, with the
+   specified address and port.  */
+static struct sockaddr *
+allocate_address_v4 (struct in_addr a, uint16_t port)
+{
+  struct sockaddr_in *sa4 = malloc (sizeof (*sa4));
+  if (sa4 == NULL)
+    return NULL;
+  sa4->sin_family = AF_INET;
+  sa4->sin_addr = a;
+  sa4->sin_port = htons (port);
+  return (struct sockaddr *) sa4;
 }
 
 /* Try to obtain the domain name from the host name and store it in
@@ -181,40 +238,17 @@  domain_from_hostname (char **result)
   return true;
 }
 
+static void res_setoptions (struct resolv_conf_parser *, const char *options);
+
 /* Internal helper function for __res_vinit, to aid with resource
    deallocation and error handling.  Return true on success, false on
    failure.  */
 static bool
-res_vinit_1 (res_state statp, bool preinit, FILE *fp,
-             struct resolv_conf_parser *parser)
+res_vinit_1 (FILE *fp, struct resolv_conf_parser *parser)
 {
   char *cp;
   size_t buffer_size = 0;
-  int nserv = 0;    /* Number of nameservers read from file.  */
-  bool have_serv6 = false;
   bool haveenv = false;
-  int nsort = 0;
-  char *net;
-
-  if (!preinit)
-    {
-      statp->retrans = RES_TIMEOUT;
-      statp->retry = RES_DFLRETRY;
-      statp->options = RES_DEFAULT;
-      statp->id = res_randomid ();
-    }
-
-  statp->nscount = 0;
-  statp->defdname[0] = '\0';
-  statp->ndots = 1;
-  statp->pfcode = 0;
-  statp->_vcsock = -1;
-  statp->_flags = 0;
-  statp->__glibc_unused_qhook = NULL;
-  statp->__glibc_unused_rhook = NULL;
-  statp->_u._ext.nscount = 0;
-  for (int n = 0; n < MAXNS; n++)
-    statp->_u._ext.nsaddrs[n] = NULL;
 
   /* Allow user to override the local domain definition.  */
   if ((cp = getenv ("LOCALDOMAIN")) != NULL)
@@ -350,19 +384,19 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
               continue;
             }
           /* Read nameservers to query.  */
-          if (MATCH (parser->buffer, "nameserver") && nserv < MAXNS)
+          if (MATCH (parser->buffer, "nameserver"))
             {
               struct in_addr a;
 
               cp = parser->buffer + sizeof ("nameserver") - 1;
               while (*cp == ' ' || *cp == '\t')
                 cp++;
+              struct sockaddr *sa;
               if ((*cp != '\0') && (*cp != '\n') && __inet_aton (cp, &a))
                 {
-                  statp->nsaddr_list[nserv].sin_addr = a;
-                  statp->nsaddr_list[nserv].sin_family = AF_INET;
-                  statp->nsaddr_list[nserv].sin_port = htons (NAMESERVER_PORT);
-                  nserv++;
+                  sa = allocate_address_v4 (a, NAMESERVER_PORT);
+                  if (sa == NULL)
+                    return false;
                 }
               else
                 {
@@ -392,13 +426,18 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
                            compatibility.  */
                         __inet6_scopeid_pton
                           (&a6, el + 1, &sa6->sin6_scope_id);
-
-                      statp->nsaddr_list[nserv].sin_family = 0;
-                      statp->_u._ext.nsaddrs[nserv] = sa6;
-                      statp->_u._ext.nssocks[nserv] = -1;
-                      have_serv6 = true;
-                      nserv++;
+                      sa = (struct sockaddr *) sa6;
                     }
+                  else
+                    /* IPv6 address parse failure.  */
+                    sa = NULL;
+                }
+              if (sa != NULL)
+                {
+                  const struct sockaddr **p = nameserver_list_emplace
+                    (&parser->nameserver_list);
+                  if (p != NULL)
+                    *p = sa;
                 }
               continue;
             }
@@ -407,21 +446,22 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
               struct in_addr a;
 
               cp = parser->buffer + sizeof ("sortlist") - 1;
-              while (nsort < MAXRESOLVSORT)
+              while (true)
                 {
                   while (*cp == ' ' || *cp == '\t')
                     cp++;
                   if (*cp == '\0' || *cp == '\n' || *cp == ';')
                     break;
-                  net = cp;
+                  char *net = cp;
                   while (*cp && !is_sort_mask (*cp) && *cp != ';'
                          && isascii (*cp) && !isspace (*cp))
                     cp++;
                   char separator = *cp;
                   *cp = 0;
+                  struct resolv_sortlist_entry e;
                   if (__inet_aton (net, &a))
                     {
-                      statp->sort_list[nsort].addr = a;
+                      e.addr = a;
                       if (is_sort_mask (separator))
                         {
                           *cp++ = separator;
@@ -432,15 +472,13 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
                           separator = *cp;
                           *cp = 0;
                           if (__inet_aton (net, &a))
-                            statp->sort_list[nsort].mask = a.s_addr;
+                            e.mask = a.s_addr;
                           else
-                            statp->sort_list[nsort].mask
-                              = net_mask (statp->sort_list[nsort].addr);
+                            e.mask = net_mask (e.addr);
                         }
                       else
-                        statp->sort_list[nsort].mask
-                          = net_mask (statp->sort_list[nsort].addr);
-                      nsort++;
+                        e.mask = net_mask (e.addr);
+                      sort_list_add (&parser->sort_list, e);
                     }
                   *cp = separator;
                 }
@@ -448,23 +486,22 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
             }
           if (MATCH (parser->buffer, "options"))
             {
-              res_setoptions (statp, parser->buffer + sizeof ("options") - 1);
+              res_setoptions (parser, parser->buffer + sizeof ("options") - 1);
               continue;
             }
         }
-      statp->nscount = nserv;
-      if (have_serv6)
-        /* We try IPv6 servers again.  */
-        statp->ipv6_unavail = false;
-      statp->nsort = nsort;
       fclose (fp);
     }
-  if (__glibc_unlikely (statp->nscount == 0))
+  if (__glibc_unlikely (nameserver_list_size (&parser->nameserver_list) == 0))
     {
-      statp->nsaddr.sin_addr = __inet_makeaddr (IN_LOOPBACKNET, 1);
-      statp->nsaddr.sin_family = AF_INET;
-      statp->nsaddr.sin_port = htons (NAMESERVER_PORT);
-      statp->nscount = 1;
+      const struct sockaddr **p
+        = nameserver_list_emplace (&parser->nameserver_list);
+      if (p == NULL)
+        return false;
+      *p = allocate_address_v4 (__inet_makeaddr (IN_LOOPBACKNET, 1),
+                                NAMESERVER_PORT);
+      if (*p == NULL)
+        return false;
     }
 
   if (search_list_size (&parser->search_list) == 0)
@@ -481,15 +518,16 @@  res_vinit_1 (res_state statp, bool preinit, FILE *fp,
     }
 
   if ((cp = getenv ("RES_OPTIONS")) != NULL)
-    res_setoptions (statp, cp);
+    res_setoptions (parser, cp);
 
-  if (search_list_has_failed (&parser->search_list))
+  if (nameserver_list_has_failed (&parser->nameserver_list)
+      || search_list_has_failed (&parser->search_list)
+      || sort_list_has_failed (&parser->sort_list))
     {
       __set_errno (ENOMEM);
       return false;
     }
 
-  statp->options |= RES_INIT;
   return true;
 }
 
@@ -525,17 +563,27 @@  __res_vinit (res_state statp, int preinit)
       }
 
   struct resolv_conf_parser parser;
-  resolv_conf_parser_init (&parser);
-  bool ok = res_vinit_1 (statp, preinit, fp, &parser);
+  if (preinit)
+    {
+      resolv_conf_parser_init (&parser, statp);
+      statp->id = res_randomid ();
+    }
+  else
+    resolv_conf_parser_init (&parser, NULL);
 
+  bool ok = res_vinit_1 (fp, &parser);
   if (ok)
     {
-      struct resolv_conf init =
-        {
-          .search_list = search_list_begin (&parser.search_list),
-          .search_list_size = search_list_size (&parser.search_list),
-        };
-      struct resolv_conf *conf = __resolv_conf_allocate (&init);
+      parser.template.nameserver_list
+        = nameserver_list_begin (&parser.nameserver_list);
+      parser.template.nameserver_list_size
+        = nameserver_list_size (&parser.nameserver_list);
+      parser.template.search_list = search_list_begin (&parser.search_list);
+      parser.template.search_list_size
+        = search_list_size (&parser.search_list);
+      parser.template.sort_list = sort_list_begin (&parser.sort_list);
+      parser.template.sort_list_size = sort_list_size (&parser.sort_list);
+      struct resolv_conf *conf = __resolv_conf_allocate (&parser.template);
       if (conf == NULL)
         ok = false;
       else
@@ -547,18 +595,13 @@  __res_vinit (res_state statp, int preinit)
   resolv_conf_parser_free (&parser);
 
   if (!ok)
-    {
-      /* Deallocate the name server addresses which have been
-         allocated.  */
-      for (int n = 0; n < MAXNS; n++)
-        free (statp->_u._ext.nsaddrs[n]);
-      return -1;
-    }
-  return 0;
+    return -1;
+  else
+    return 0;
 }
 
 static void
-res_setoptions (res_state statp, const char *options)
+res_setoptions (struct resolv_conf_parser *parser, const char *options)
 {
   const char *cp = options;
 
@@ -572,25 +615,25 @@  res_setoptions (res_state statp, const char *options)
         {
           int i = atoi (cp + sizeof ("ndots:") - 1);
           if (i <= RES_MAXNDOTS)
-            statp->ndots = i;
+            parser->template.ndots = i;
           else
-            statp->ndots = RES_MAXNDOTS;
+            parser->template.ndots = RES_MAXNDOTS;
         }
       else if (!strncmp (cp, "timeout:", sizeof ("timeout:") - 1))
         {
           int i = atoi (cp + sizeof ("timeout:") - 1);
           if (i <= RES_MAXRETRANS)
-            statp->retrans = i;
+            parser->template.retrans = i;
           else
-            statp->retrans = RES_MAXRETRANS;
+            parser->template.retrans = RES_MAXRETRANS;
         }
       else if (!strncmp (cp, "attempts:", sizeof ("attempts:") - 1))
         {
           int i = atoi (cp + sizeof ("attempts:") - 1);
           if (i <= RES_MAXRETRY)
-            statp->retry = i;
+            parser->template.retry = i;
           else
-            statp->retry = RES_MAXRETRY;
+            parser->template.retry = RES_MAXRETRY;
         }
       else
         {
@@ -616,9 +659,9 @@  res_setoptions (res_state statp, const char *options)
             if (strncmp (cp, options[i].str, options[i].len) == 0)
               {
                 if (options[i].clear)
-                  statp->options &= options[i].flag;
+                  parser->template.options &= options[i].flag;
                 else
-                  statp->options |= options[i].flag;
+                  parser->template.options |= options[i].flag;
                 break;
               }
         }
diff --git a/resolv/resolv_conf.c b/resolv/resolv_conf.c
index 3a730ff..cd2f60d 100644
--- a/resolv/resolv_conf.c
+++ b/resolv/resolv_conf.c
@@ -106,11 +106,80 @@  resolv_conf_get_1 (const struct __res_state *resp)
   return conf;
 }
 
+/* Return true if both IPv4 addresses are equal.  */
+static bool
+same_address_v4 (const struct sockaddr_in *left,
+                 const struct sockaddr_in *right)
+{
+  return left->sin_addr.s_addr == right->sin_addr.s_addr
+    && left->sin_port == right->sin_port;
+}
+
+/* Return true if both IPv6 addresses are equal.  This ignores the
+   flow label.  */
+static bool
+same_address_v6 (const struct sockaddr_in6 *left,
+                 const struct sockaddr_in6 *right)
+{
+  return memcmp (&left->sin6_addr, &right->sin6_addr,
+                 sizeof (left->sin6_addr)) == 0
+    && left->sin6_port == right->sin6_port
+    && left->sin6_scope_id == right->sin6_scope_id;
+}
+
+static bool
+same_address (const struct sockaddr *left, const struct sockaddr *right)
+{
+  if (left->sa_family != right->sa_family)
+    return false;
+  switch (left->sa_family)
+    {
+    case AF_INET:
+      return same_address_v4 ((const struct sockaddr_in *) left,
+                              (const struct sockaddr_in *) right);
+    case AF_INET6:
+      return same_address_v6 ((const struct sockaddr_in6 *) left,
+                              (const struct sockaddr_in6 *) right);
+    }
+  return false;
+}
+
 /* Check that *RESP and CONF match.  Used by __resolv_conf_get.  */
 static bool
 resolv_conf_matches (const struct __res_state *resp,
                      const struct resolv_conf *conf)
 {
+  /* NB: Do not compare the options, retrans, retry, ndots.  These can
+     be changed by applicaiton.  */
+
+  /* Check that the name servers in *RESP have not been modified by
+     the application.  */
+  {
+    size_t nserv = conf->nameserver_list_size;
+    if (nserv > MAXNS)
+      nserv = MAXNS;
+    /* _ext.nscount is 0 until initialized by res_send.c.  */
+    if (resp->nscount != nserv
+        && (resp->_u._ext.nscount != 0 && resp->_u._ext.nscount != nserv))
+      return false;
+    for (size_t i = 0; i < nserv; ++i)
+      {
+        if (resp->nsaddr_list[i].sin_family == 0)
+          {
+            if (resp->_u._ext.nsaddrs[i]->sin6_family != AF_INET6)
+              return false;
+            if (!same_address ((struct sockaddr *) resp->_u._ext.nsaddrs[i],
+                               conf->nameserver_list[i]))
+              return false;
+          }
+        else if (resp->nsaddr_list[i].sin_family != AF_INET)
+          return false;
+        else if (!same_address ((struct sockaddr *) &resp->nsaddr_list[i],
+                                conf->nameserver_list[i]))
+          return false;
+      }
+  }
+
   /* Check that the search list in *RESP has not been modified by the
      application.  */
   {
@@ -139,6 +208,18 @@  resolv_conf_matches (const struct __res_state *resp,
           }
       }
   }
+
+  /* Check that the sort list has not been modified.  */
+  {
+    size_t nsort = conf->sort_list_size;
+    if (nsort > MAXRESOLVSORT)
+      nsort = MAXRESOLVSORT;
+    for (size_t i = 0; i < nsort; ++i)
+      if (resp->sort_list[i].addr.s_addr != conf->sort_list[i].addr.s_addr
+          || resp->sort_list[i].mask != conf->sort_list[i].mask)
+        return false;
+  }
+
   return true;
 }
 
@@ -172,7 +253,28 @@  __resolv_conf_put (struct resolv_conf *conf)
 struct resolv_conf *
 __resolv_conf_allocate (const struct resolv_conf *init)
 {
-  /* Space needed by the strings.  */
+  /* Allocate in decreasing order of alignment.  */
+  _Static_assert (__alignof__ (const char *const *)
+                  <= __alignof__ (struct resolv_conf), "alignment");
+  _Static_assert (__alignof__ (struct sockaddr_in6)
+                  <= __alignof__ (const char *const *), "alignment");
+  _Static_assert (__alignof__ (struct sockaddr_in)
+                  ==  __alignof__ (struct sockaddr_in6), "alignment");
+  _Static_assert (__alignof__ (struct resolv_sortlist_entry)
+                  <= __alignof__ (struct sockaddr_in), "alignment");
+
+  /* Space needed by the nameserver addresses.  */
+  size_t address_space = 0;
+  for (size_t i = 0; i < init->nameserver_list_size; ++i)
+    if (init->nameserver_list[i]->sa_family == AF_INET)
+      address_space += sizeof (struct sockaddr_in);
+    else
+      {
+        assert (init->nameserver_list[i]->sa_family == AF_INET6);
+        address_space += sizeof (struct sockaddr_in6);
+      }
+
+  /* Space needed by the search list strings.  */
   size_t string_space = 0;
   for (size_t i = 0; i < init->search_list_size; ++i)
     string_space += strlen (init->search_list[i]) + 1;
@@ -181,7 +283,10 @@  __resolv_conf_allocate (const struct resolv_conf *init)
   void *ptr;
   struct alloc_buffer buffer = alloc_buffer_allocate
     (sizeof (struct resolv_conf)
+     + init->nameserver_list_size * sizeof (init->nameserver_list[0])
+     + address_space
      + init->search_list_size * sizeof (init->search_list[0])
+     + init->sort_list_size * sizeof (init->sort_list[0])
      + string_space,
      &ptr);
   struct resolv_conf *conf
@@ -193,16 +298,57 @@  __resolv_conf_allocate (const struct resolv_conf *init)
 
   /* Initialize the contents.  */
   conf->__refcount = 1;
+  conf->retrans = init->retrans;
+  conf->retry = init->retry;
+  conf->options = init->options;
+  conf->ndots = init->ndots;
   conf->initstamp = __res_initstamp;
 
-  /* Allocate and fill the search list array.  */
+  /* Allocate the arrays with pointers.  These must come first because
+     they have the highets alignment.  */
+  conf->nameserver_list_size = init->nameserver_list_size;
+  const struct sockaddr **nameserver_array = alloc_buffer_alloc_array
+    (&buffer, const struct sockaddr *, init->nameserver_list_size);
+  conf->nameserver_list = nameserver_array;
+
+  conf->search_list_size = init->search_list_size;
+  const char **search_array = alloc_buffer_alloc_array
+    (&buffer, const char *, init->search_list_size);
+  conf->search_list = search_array;
+
+  /* Fill the name server list array.  */
+  for (size_t i = 0; i < init->nameserver_list_size; ++i)
+    if (init->nameserver_list[i]->sa_family == AF_INET)
+      {
+        struct sockaddr_in *sa = alloc_buffer_alloc
+          (&buffer, struct sockaddr_in);
+        *sa = *(struct sockaddr_in *) init->nameserver_list[i];
+        nameserver_array[i] = (struct sockaddr *) sa;
+      }
+    else
+      {
+        struct sockaddr_in6 *sa = alloc_buffer_alloc
+          (&buffer, struct sockaddr_in6);
+        *sa = *(struct sockaddr_in6 *) init->nameserver_list[i];
+        nameserver_array[i] = (struct sockaddr *) sa;
+      }
+
+  /* Allocate and fill the sort list array.  */
+  {
+    conf->sort_list_size = init->sort_list_size;
+    struct resolv_sortlist_entry *array = alloc_buffer_alloc_array
+      (&buffer, struct resolv_sortlist_entry, init->sort_list_size);
+    conf->sort_list = array;
+    for (size_t i = 0; i < init->sort_list_size; ++i)
+      array[i] = init->sort_list[i];
+  }
+
+  /* Fill the search list array.  This must come last because the
+     strings are the least aligned part of the allocation.  */
   {
-    conf->search_list_size = init->search_list_size;
-    const char **array = alloc_buffer_alloc_array
-      (&buffer, const char *, init->search_list_size);
-    conf->search_list = array;
     for (size_t i = 0; i < init->search_list_size; ++i)
-      array[i] = alloc_buffer_copy_string (&buffer, init->search_list[i]);
+      search_array[i] = alloc_buffer_copy_string
+        (&buffer, init->search_list[i]);
   }
 
   assert (!alloc_buffer_has_failed (&buffer));
@@ -213,6 +359,56 @@  __resolv_conf_allocate (const struct resolv_conf *init)
 static __attribute__ ((nonnull (1, 2), warn_unused_result)) bool
 update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
 {
+  resp->defdname[0] = '\0';
+  resp->pfcode = 0;
+  resp->_vcsock = -1;
+  resp->_flags = 0;
+  resp->ipv6_unavail = false;
+  resp->__glibc_unused_qhook = NULL;
+  resp->__glibc_unused_rhook = NULL;
+
+  resp->retrans = conf->retrans;
+  resp->retry = conf->retry;
+  resp->options = conf->options;
+  resp->ndots = conf->ndots;
+
+  /* Copy the name server addresses.  */
+  {
+    resp->nscount = 0;
+    resp->_u._ext.nscount = 0;
+    size_t nserv = conf->nameserver_list_size;
+    if (nserv > MAXNS)
+      nserv = MAXNS;
+    for (size_t i = 0; i < nserv; i++)
+      {
+        if (conf->nameserver_list[i]->sa_family == AF_INET)
+          {
+            resp->nsaddr_list[i]
+              = *(struct sockaddr_in *)conf->nameserver_list[i];
+            resp->_u._ext.nsaddrs[i] = NULL;
+          }
+        else
+          {
+            assert (conf->nameserver_list[i]->sa_family == AF_INET6);
+            resp->nsaddr_list[i].sin_family = 0;
+            /* Make a defensive copy of the name server address, in
+               case the application overwrites it.  */
+            struct sockaddr_in6 *sa = malloc (sizeof (*sa));
+            if (sa == NULL)
+              {
+                for (size_t j = 0; j < i; ++j)
+                  free (resp->_u._ext.nsaddrs[j]);
+                return false;
+              }
+            *sa = *(struct sockaddr_in6 *)conf->nameserver_list[i];
+            resp->_u._ext.nsaddrs[i] = sa;
+          }
+        resp->_u._ext.nssocks[i] = -1;
+      }
+    resp->nscount = nserv;
+    /* Leave resp->_u._ext.nscount at 0.  res_send.c handles this.  */
+  }
+
   /* Fill in the prefix of the search list.  It is truncated either at
      MAXDNSRCH, or if reps->defdname has insufficient space.  */
   {
@@ -231,6 +427,19 @@  update_from_conf (struct __res_state *resp, const struct resolv_conf *conf)
     resp->dnsrch[i] = NULL;
   }
 
+  /* Copy the sort list.  */
+  {
+    size_t nsort = conf->sort_list_size;
+    if (nsort > MAXRESOLVSORT)
+      nsort = MAXRESOLVSORT;
+    for (size_t i = 0; i < nsort; ++i)
+      {
+        resp->sort_list[i].addr = conf->sort_list[i].addr;
+        resp->sort_list[i].mask = conf->sort_list[i].mask;
+      }
+    resp->nsort = nsort;
+  }
+
   /* The overlapping parts of both configurations should agree after
      initialization.  */
   assert (resolv_conf_matches (resp, conf));
diff --git a/resolv/resolv_conf.h b/resolv/resolv_conf.h
index 80a0b93..7ca80cd 100644
--- a/resolv/resolv_conf.h
+++ b/resolv/resolv_conf.h
@@ -19,9 +19,17 @@ 
 #ifndef RESOLV_STATE_H
 #define RESOLV_STATE_H
 
+#include <netinet/in.h>
 #include <stdbool.h>
 #include <stddef.h>
 
+/* This type corresponds to members of the _res.sort_list array.  */
+struct resolv_sortlist_entry
+{
+  struct in_addr addr;
+  uint32_t mask;
+};
+
 /* Extended resolver state associated with res_state objects.  Client
    code can reach this state through a struct resolv_context
    object.  */
@@ -36,9 +44,24 @@  struct resolv_conf
      zero.  For internal use within resolv_conf only.  */
   size_t __refcount;
 
+  /* List of IPv4 and IPv6 name server addresses.  */
+  const struct sockaddr **nameserver_list;
+  size_t nameserver_list_size;
+
   /* The domain names forming the search list.  */
   const char *const *search_list;
   size_t search_list_size;
+
+  /* IPv4 address preference rules.  */
+  const struct resolv_sortlist_entry *sort_list;
+  size_t sort_list_size;
+
+  /* _res.options has type unsigned long, but we can only use 32 bits
+     for portability across all architectures.  */
+  unsigned int options;
+  unsigned int retrans;         /* Timeout.  */
+  unsigned int retry;           /* Number of times to retry.  */
+  unsigned int ndots; /* Dots needed for initial non-search query.  */
 };
 
 /* The functions below are for use by the res_init resolv.conf parser
diff --git a/resolv/resolv_context.h b/resolv/resolv_context.h
index 0f4d47d..e2f7ad6 100644
--- a/resolv/resolv_context.h
+++ b/resolv/resolv_context.h
@@ -112,6 +112,67 @@  __resolv_context_search_list (const struct resolv_context *ctx, size_t index)
   return NULL;
 }
 
+/* Return the number of name servers.  */
+static __attribute__ ((nonnull (1), unused)) size_t
+__resolv_context_nameserver_count (const struct resolv_context *ctx)
+{
+  if (ctx->conf != NULL)
+    return ctx->conf->nameserver_list_size;
+  else
+    return ctx->resp->nscount;
+}
+
+/* Return a pointer to the socket address of the name server INDEX, or
+   NULL if the index is out of bounds.  */
+static __attribute__ ((nonnull (1), unused)) const struct sockaddr *
+__resolv_context_nameserver (const struct resolv_context *ctx, size_t index)
+{
+  if (ctx->conf != NULL)
+    {
+      if (index < ctx->conf->nameserver_list_size)
+        return ctx->conf->nameserver_list[index];
+    }
+  else
+    if (index < ctx->resp->nscount)
+      {
+        if (ctx->resp->nsaddr_list[index].sin_family != 0)
+          return (const struct sockaddr *) &ctx->resp->nsaddr_list[index];
+        else
+          return (const struct sockaddr *) &ctx->resp->_u._ext.nsaddrs[index];
+      }
+  return NULL;
+}
+
+/* Return the number of sort list entries.  */
+static __attribute__ ((nonnull (1), unused)) size_t
+__resolv_context_sort_count (const struct resolv_context *ctx)
+{
+  if (ctx->conf != NULL)
+    return ctx->conf->sort_list_size;
+  else
+    return ctx->resp->nsort;
+}
+
+/* Return the sort list entry at INDEX.  */
+static __attribute__ ((nonnull (1), unused)) struct resolv_sortlist_entry
+__resolv_context_sort_entry (const struct resolv_context *ctx, size_t index)
+{
+  if (ctx->conf != NULL)
+    {
+      if (index < ctx->conf->sort_list_size)
+        return ctx->conf->sort_list[index];
+      /* Fall through.  */
+    }
+  else if (index < ctx->resp->nsort)
+    return (struct resolv_sortlist_entry)
+      {
+        .addr = ctx->resp->sort_list[index].addr,
+        .mask = ctx->resp->sort_list[index].mask,
+      };
+
+  return (struct resolv_sortlist_entry) { .mask = 0, };
+}
+
 /* Called during thread shutdown to free the associated resolver
    context (mostly in response to cancellation, otherwise the
    __resolv_context_get/__resolv_context_put pairing will already have
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
index d377a6b..aace94d 100644
--- a/resolv/tst-resolv-res_init-skeleton.c
+++ b/resolv/tst-resolv-res_init-skeleton.c
@@ -242,6 +242,46 @@  print_resp (FILE *fp, res_state resp)
         }
     }
 
+  /* The extended name server list.  */
+  {
+    size_t i = 0;
+    while (true)
+      {
+        const struct sockaddr *addr = __resolv_context_nameserver (ctx, i);
+        if (addr == NULL)
+          break;
+        size_t addrlen;
+        switch (addr->sa_family)
+          {
+          case AF_INET:
+            addrlen = sizeof (struct sockaddr_in);
+            break;
+          case AF_INET6:
+            addrlen = sizeof (struct sockaddr_in6);
+            break;
+          default:
+            FAIL_EXIT1 ("invalid address family %d", addr->sa_family);
+          }
+
+        char host[NI_MAXHOST];
+        char service[NI_MAXSERV];
+        int ret = getnameinfo (addr, addrlen,
+                               host, sizeof (host), service, sizeof (service),
+                               NI_NUMERICHOST | NI_NUMERICSERV);
+
+        if (ret != 0)
+          {
+            if (ret == EAI_SYSTEM)
+              fprintf (fp, "; error: getnameinfo: %m\n");
+            else
+              fprintf (fp, "; error: getnameinfo: %s\n", gai_strerror (ret));
+          }
+        else
+          fprintf (fp, "; nameserver[%zu]: [%s]:%s\n", i, host, service);
+        ++i;
+      }
+  }
+
   TEST_VERIFY (!ferror (fp));
 
   __resolv_context_put (ctx);
@@ -391,12 +431,14 @@  struct test_case test_cases[] =
      .expected = "search example.com\n"
      "; search[0]: example.com\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
     },
     {.name = "empty file with LOCALDOMAIN",
      .conf = "",
      .expected = "search example.net\n"
      "; search[0]: example.net\n"
-     "nameserver 127.0.0.1\n",
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .localdomain = "example.net",
     },
     {.name = "empty file with RES_OPTIONS",
@@ -404,7 +446,8 @@  struct test_case test_cases[] =
      .expected = "options attempts:5 edns0\n"
      "search example.com\n"
      "; search[0]: example.com\n"
-     "nameserver 127.0.0.1\n",
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .res_options = "edns0 attempts:5",
     },
     {.name = "empty file with RES_OPTIONS and LOCALDOMAIN",
@@ -412,7 +455,8 @@  struct test_case test_cases[] =
      .expected = "options attempts:5 edns0\n"
      "search example.org\n"
      "; search[0]: example.org\n"
-     "nameserver 127.0.0.1\n",
+     "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n",
      .localdomain = "example.org",
      .res_options = "edns0 attempts:5",
     },
@@ -424,6 +468,7 @@  struct test_case test_cases[] =
      "; search[0]: corp.example.com\n"
      "; search[1]: example.com\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "whitespace",
      .conf = "# This test covers comment and whitespace processing "
@@ -440,6 +485,8 @@  struct test_case test_cases[] =
      "; search[1]: example.com\n"
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.2\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.2]:53\n"
     },
     {.name = "domain",
      .conf = "domain example.net\n"
@@ -447,6 +494,7 @@  struct test_case test_cases[] =
      .expected = "search example.net\n"
      "; search[0]: example.net\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "domain space",
      .conf = "domain example.net \n"
@@ -454,6 +502,7 @@  struct test_case test_cases[] =
      .expected = "search example.net\n"
      "; search[0]: example.net\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "domain tab",
      .conf = "domain example.net\t\n"
@@ -461,6 +510,7 @@  struct test_case test_cases[] =
      .expected = "search example.net\n"
      "; search[0]: example.net\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "domain override",
      .conf = "search example.com example.org\n"
@@ -469,6 +519,7 @@  struct test_case test_cases[] =
      .expected = "search example.net\n"
      "; search[0]: example.net\n"
      "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "option values, multiple servers",
      .conf = "options\tinet6\tndots:3 edns0\tattempts:5\ttimeout:19\n"
@@ -485,6 +536,9 @@  struct test_case test_cases[] =
      "nameserver 192.0.2.1\n"
      "nameserver ::1\n"
      "nameserver 192.0.2.2\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [::1]:53\n"
+     "; nameserver[2]: [192.0.2.2]:53\n"
     },
     {.name = "out-of-range option vales",
      .conf = "options use-vc timeout:999 attempts:999 ndots:99\n"
@@ -493,6 +547,7 @@  struct test_case test_cases[] =
      "search example.com\n"
      "; search[0]: example.com\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
     },
     {.name = "repeated directives",
      .conf = "options ndots:3 use-vc\n"
@@ -505,6 +560,7 @@  struct test_case test_cases[] =
      "search example.org\n"
      "; search[0]: example.org\n"
      "nameserver 127.0.0.1\n"
+     "; nameserver[0]: [127.0.0.1]:53\n"
     },
     {.name = "many name servers, sortlist",
      .conf = "options single-request\n"
@@ -528,6 +584,14 @@  struct test_case test_cases[] =
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.2\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.2]:53\n"
+     "; nameserver[2]: [192.0.2.3]:53\n"
+     "; nameserver[3]: [192.0.2.4]:53\n"
+     "; nameserver[4]: [192.0.2.5]:53\n"
+     "; nameserver[5]: [192.0.2.6]:53\n"
+     "; nameserver[6]: [192.0.2.7]:53\n"
+     "; nameserver[7]: [192.0.2.8]:53\n"
     },
     {.name = "IPv4 and IPv6 nameservers",
      .conf = "options single-request\n"
@@ -554,6 +618,14 @@  struct test_case test_cases[] =
      "nameserver 192.0.2.1\n"
      "nameserver 2001:db8::2\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [2001:db8::2]:53\n"
+     "; nameserver[2]: [192.0.2.3]:53\n"
+     "; nameserver[3]: [2001:db8::4]:53\n"
+     "; nameserver[4]: [192.0.2.5]:53\n"
+     "; nameserver[5]: [2001:db8::6]:53\n"
+     "; nameserver[6]: [192.0.2.7]:53\n"
+     "; nameserver[7]: [2001:db8::8]:53\n",
     },
     {.name = "garbage after nameserver",
      .conf = "nameserver 192.0.2.1 garbage\n"
@@ -563,6 +635,8 @@  struct test_case test_cases[] =
      "; search[0]: example.com\n"
      "nameserver 192.0.2.1\n"
      "nameserver 192.0.2.3\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
+     "; nameserver[1]: [192.0.2.3]:53\n"
     },
     {.name = "RES_OPTIONS is cummulative",
      .conf = "options timeout:7 ndots:2 use-vc\n"
@@ -570,7 +644,8 @@  struct test_case test_cases[] =
      .expected = "options ndots:3 timeout:7 attempts:5 use-vc edns0\n"
      "search example.com\n"
      "; search[0]: example.com\n"
-     "nameserver 192.0.2.1\n",
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n",
      .res_options = "attempts:5 ndots:3 edns0 ",
     },
     {.name = "many search list entries (bug 19569)",
@@ -588,7 +663,8 @@  struct test_case test_cases[] =
      "; search[5]: example.com\n"
      "; search[6]: example.org\n"
      "; search[7]: example.net\n"
-     "nameserver 192.0.2.1\n",
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     {.name = "very long search list entries (bug 21475)",
      .conf = "nameserver 192.0.2.1\n"
@@ -603,7 +679,8 @@  struct test_case test_cases[] =
      "; search[2]: " H63 "." D63 ".example.net\n"
 #undef H63
 #undef D63
-     "nameserver 192.0.2.1\n",
+     "nameserver 192.0.2.1\n"
+     "; nameserver[0]: [192.0.2.1]:53\n"
     },
     { NULL }
   };