c5d972
commit f282cdbe7f436c75864e5640a409a10485e9abb2
c5d972
Author: Florian Weimer <fweimer@redhat.com>
c5d972
Date:   Fri Jun 24 18:16:41 2022 +0200
c5d972
c5d972
    resolv: Implement no-aaaa stub resolver option
c5d972
c5d972
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
c5d972
c5d972
Conflicts:
c5d972
	resolv/Makefile
c5d972
	  (missing partial libresolv integration downstream)
c5d972
	resolv/res-noaaaa.c
c5d972
	  (call ns_name_skip instead of __ns_name_skip (not available
c5d972
	  downstream) and ns_name_unpack instead of __ns_name_unpack
c5d972
	  (avoid PLT))
c5d972
	resolv/res_debug.c
c5d972
	resolv/res_init.c
c5d972
	resolv/resolv.h
c5d972
	resolv/tst-resolv-res_init-skeleton.c
c5d972
	  (missing trust-ad support downstream)
c5d972
c5d972
diff --git a/resolv/Makefile b/resolv/Makefile
c5d972
index cee5225f8933f245..ab8ad49b5318ad41 100644
c5d972
--- a/resolv/Makefile
c5d972
+++ b/resolv/Makefile
c5d972
@@ -57,6 +57,7 @@ tests += \
c5d972
   tst-resolv-binary \
c5d972
   tst-resolv-edns \
c5d972
   tst-resolv-network \
c5d972
+  tst-resolv-noaaaa \
c5d972
   tst-resolv-nondecimal \
c5d972
   tst-resolv-res_init-multi \
c5d972
   tst-resolv-search \
c5d972
@@ -110,7 +111,7 @@ libresolv-routines := res_comp res_debug \
c5d972
 		      res_data res_mkquery res_query res_send		\
c5d972
 		      inet_net_ntop inet_net_pton inet_neta base64	\
c5d972
 		      ns_parse ns_name ns_netint ns_ttl ns_print	\
c5d972
-		      ns_samedomain ns_date res_enable_icmp \
c5d972
+		      ns_samedomain ns_date res_enable_icmp res-noaaaa  \
c5d972
 		      compat-hooks compat-gethnamaddr
c5d972
 
c5d972
 libanl-routines := gai_cancel gai_error gai_misc gai_notify gai_suspend \
c5d972
@@ -200,6 +201,7 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \
c5d972
   $(shared-thread-library)
c5d972
 $(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \
c5d972
   $(shared-thread-library)
c5d972
+$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
c5d972
 $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
c5d972
 $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
c5d972
 $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
c5d972
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
c5d972
index 99c3b61e1cee4d42..ff0a0b6f7f1f4703 100644
c5d972
--- a/resolv/nss_dns/dns-host.c
c5d972
+++ b/resolv/nss_dns/dns-host.c
c5d972
@@ -123,6 +123,14 @@ static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
c5d972
 				       char *buffer, size_t buflen,
c5d972
 				       int *errnop, int *h_errnop,
c5d972
 				       int32_t *ttlp);
c5d972
+static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1,
c5d972
+					      int anslen1,
c5d972
+					      const char *qname,
c5d972
+					      struct gaih_addrtuple **pat,
c5d972
+					      char *buffer, size_t buflen,
c5d972
+					      int *errnop, int *h_errnop,
c5d972
+					      int32_t *ttlp);
c5d972
+
c5d972
 
c5d972
 static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
c5d972
 					       const char *name, int af,
c5d972
@@ -367,17 +375,31 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
c5d972
   int resplen2 = 0;
c5d972
   int ans2p_malloced = 0;
c5d972
 
c5d972
+
c5d972
   int olderr = errno;
c5d972
-  int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
c5d972
+  int n;
c5d972
+
c5d972
+  if ((ctx->resp->options & RES_NOAAAA) == 0)
c5d972
+    {
c5d972
+      n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
c5d972
 				host_buffer.buf->buf, 2048, &host_buffer.ptr,
c5d972
 				&ans2p, &nans2p, &resplen2, &ans2p_malloced);
c5d972
-  if (n >= 0)
c5d972
-    {
c5d972
-      status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
c5d972
-			       resplen2, name, pat, buffer, buflen,
c5d972
-			       errnop, herrnop, ttlp);
c5d972
+      if (n >= 0)
c5d972
+	status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
c5d972
+				 resplen2, name, pat, buffer, buflen,
c5d972
+				 errnop, herrnop, ttlp);
c5d972
     }
c5d972
   else
c5d972
+    {
c5d972
+      n = __res_context_search (ctx, name, C_IN, T_A,
c5d972
+				host_buffer.buf->buf, 2048, NULL,
c5d972
+				NULL, NULL, NULL, NULL);
c5d972
+      if (n >= 0)
c5d972
+	status = gaih_getanswer_noaaaa (host_buffer.buf, n,
c5d972
+					name, pat, buffer, buflen,
c5d972
+					errnop, herrnop, ttlp);
c5d972
+    }
c5d972
+  if (n < 0)
c5d972
     {
c5d972
       switch (errno)
c5d972
 	{
c5d972
@@ -1386,3 +1408,21 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
c5d972
 
c5d972
   return status;
c5d972
 }
c5d972
+
c5d972
+/* Variant of gaih_getanswer without a second (AAAA) response.  */
c5d972
+static enum nss_status
c5d972
+gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname,
c5d972
+		       struct gaih_addrtuple **pat,
c5d972
+		       char *buffer, size_t buflen,
c5d972
+		       int *errnop, int *h_errnop, int32_t *ttlp)
c5d972
+{
c5d972
+  int first = 1;
c5d972
+
c5d972
+  enum nss_status status = NSS_STATUS_NOTFOUND;
c5d972
+  if (anslen1 > 0)
c5d972
+    status = gaih_getanswer_slice (answer1, anslen1, qname,
c5d972
+				   &pat, &buffer, &buflen,
c5d972
+				   errnop, h_errnop, ttlp,
c5d972
+				   &first);
c5d972
+  return status;
c5d972
+}
c5d972
diff --git a/resolv/res-noaaaa.c b/resolv/res-noaaaa.c
c5d972
new file mode 100644
c5d972
index 0000000000000000..e2a13cf38a74c160
c5d972
--- /dev/null
c5d972
+++ b/resolv/res-noaaaa.c
c5d972
@@ -0,0 +1,143 @@
c5d972
+/* Implement suppression of AAAA queries.
c5d972
+   Copyright (C) 2022 Free Software Foundation, Inc.
c5d972
+   This file is part of the GNU C Library.
c5d972
+
c5d972
+   The GNU C Library is free software; you can redistribute it and/or
c5d972
+   modify it under the terms of the GNU Lesser General Public
c5d972
+   License as published by the Free Software Foundation; either
c5d972
+   version 2.1 of the License, or (at your option) any later version.
c5d972
+
c5d972
+   The GNU C Library is distributed in the hope that it will be useful,
c5d972
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
c5d972
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
c5d972
+   Lesser General Public License for more details.
c5d972
+
c5d972
+   You should have received a copy of the GNU Lesser General Public
c5d972
+   License along with the GNU C Library; if not, see
c5d972
+   <https://www.gnu.org/licenses/>.  */
c5d972
+
c5d972
+#include <resolv.h>
c5d972
+#include <string.h>
c5d972
+#include <resolv-internal.h>
c5d972
+#include <resolv_context.h>
c5d972
+#include <arpa/nameser.h>
c5d972
+
c5d972
+/* Returns true if the question type at P matches EXPECTED, and the
c5d972
+   class is IN.  */
c5d972
+static bool
c5d972
+qtype_matches (const unsigned char *p, int expected)
c5d972
+{
c5d972
+  /* This assumes that T_A/C_IN constants are less than 256, which
c5d972
+     they are.  */
c5d972
+  return p[0] == 0 && p[1] == expected && p[2] == 0 && p[3] == C_IN;
c5d972
+}
c5d972
+
c5d972
+/* Handle RES_NOAAAA translation of AAAA queries.  To produce a Name
c5d972
+   Error (NXDOMAIN) repsonse for domain names that do not exist, it is
c5d972
+   still necessary to send a query.  Using question type A is a
c5d972
+   conservative choice.  In the returned answer, it is necessary to
c5d972
+   switch back the question type to AAAA.  */
c5d972
+bool
c5d972
+__res_handle_no_aaaa (struct resolv_context *ctx,
c5d972
+                      const unsigned char *buf, int buflen,
c5d972
+                      unsigned char *ans, int anssiz, int *result)
c5d972
+{
c5d972
+  /* AAAA mode is not active, or the query looks invalid (will not be
c5d972
+     able to be parsed).  */
c5d972
+  if ((ctx->resp->options & RES_NOAAAA) == 0
c5d972
+      || buflen <= sizeof (HEADER))
c5d972
+    return false;
c5d972
+
c5d972
+  /* The replacement A query is produced here.  */
c5d972
+  struct
c5d972
+  {
c5d972
+    HEADER header;
c5d972
+    unsigned char question[NS_MAXCDNAME + 4];
c5d972
+  } replacement;
c5d972
+  memcpy (&replacement.header, buf, sizeof (replacement.header));
c5d972
+
c5d972
+  if (replacement.header.qr
c5d972
+      || replacement.header.opcode != 0
c5d972
+      || replacement.header.rcode != 0
c5d972
+      || ntohs (replacement.header.qdcount) != 1
c5d972
+      || ntohs (replacement.header.ancount) != 0
c5d972
+      || ntohs (replacement.header.nscount) != 0)
c5d972
+    /* Not a well-formed question.  Let the core resolver code produce
c5d972
+       the proper error.  */
c5d972
+    return false;
c5d972
+
c5d972
+  /* Disable EDNS0.  */
c5d972
+  replacement.header.arcount = htons (0);
c5d972
+
c5d972
+  /* Extract the QNAME.  */
c5d972
+  int ret = ns_name_unpack (buf, buf + buflen, buf + sizeof (HEADER),
c5d972
+                              replacement.question, NS_MAXCDNAME);
c5d972
+  if (ret < 0)
c5d972
+    /* Format error.  */
c5d972
+    return false;
c5d972
+
c5d972
+  /* Compute the end of the question name.  */
c5d972
+  const unsigned char *after_question = buf + sizeof (HEADER) + ret;
c5d972
+
c5d972
+  /* Check that we are dealing with an AAAA query.  */
c5d972
+  if (buf + buflen - after_question < 4
c5d972
+      || !qtype_matches (after_question, T_AAAA))
c5d972
+    return false;
c5d972
+
c5d972
+  /* Find the place to store the type/class data in the replacement
c5d972
+     query.  */
c5d972
+  after_question = replacement.question;
c5d972
+  /* This cannot fail because ns_name_unpack above produced a valid
c5d972
+     domain name.  */
c5d972
+  (void) ns_name_skip (&after_question, &replacement.question[NS_MAXCDNAME]);
c5d972
+  unsigned char *start_of_query = (unsigned char *) &replacement;
c5d972
+  const unsigned char *end_of_query = after_question + 4;
c5d972
+
c5d972
+  /* Produce an A/IN query.  */
c5d972
+  {
c5d972
+    unsigned char *p = (unsigned char *) after_question;
c5d972
+    p[0] = 0;
c5d972
+    p[1] = T_A;
c5d972
+    p[2] = 0;
c5d972
+    p[3] = C_IN;
c5d972
+  }
c5d972
+
c5d972
+  /* Clear the output buffer, to avoid reading undefined data when
c5d972
+     rewriting the result from A to AAAA.  */
c5d972
+  memset (ans, 0, anssiz);
c5d972
+
c5d972
+  /* Always perform the message translation, independent of the error
c5d972
+     code.  */
c5d972
+  ret = __res_context_send (ctx,
c5d972
+                            start_of_query, end_of_query - start_of_query,
c5d972
+                            NULL, 0, ans, anssiz,
c5d972
+                            NULL, NULL, NULL, NULL, NULL);
c5d972
+
c5d972
+  /* Patch in the AAAA question type if there is room and the A query
c5d972
+     type was received.  */
c5d972
+  after_question = ans + sizeof (HEADER);
c5d972
+  if (ns_name_skip (&after_question, ans + anssiz) == 0
c5d972
+      && ans + anssiz - after_question >= 4
c5d972
+      && qtype_matches (after_question, T_A))
c5d972
+    {
c5d972
+      ((unsigned char *) after_question)[1] = T_AAAA;
c5d972
+
c5d972
+      /* Create an aligned copy of the header.  Hide all data except
c5d972
+         the question from the response.  Put back the header.  There is
c5d972
+         no need to change the response code.  The zero answer count turns
c5d972
+         a positive response with data into a no-data response.  */
c5d972
+      memcpy (&replacement.header, ans, sizeof (replacement.header));
c5d972
+      replacement.header.ancount = htons (0);
c5d972
+      replacement.header.nscount = htons (0);
c5d972
+      replacement.header.arcount = htons (0);
c5d972
+      memcpy (ans, &replacement.header, sizeof (replacement.header));
c5d972
+
c5d972
+      /* Truncate the reply.  */
c5d972
+      if (ret <= 0)
c5d972
+        *result = ret;
c5d972
+      else
c5d972
+        *result = after_question - ans + 4;
c5d972
+    }
c5d972
+
c5d972
+  return true;
c5d972
+}
c5d972
diff --git a/resolv/res_debug.c b/resolv/res_debug.c
c5d972
index 7681ad4639d8a7bc..43b3b1bfe4afdcaf 100644
c5d972
--- a/resolv/res_debug.c
c5d972
+++ b/resolv/res_debug.c
c5d972
@@ -615,6 +615,7 @@ p_option(u_long option) {
c5d972
 	case RES_USE_DNSSEC:	return "dnssec";
c5d972
 	case RES_NOTLDQUERY:	return "no-tld-query";
c5d972
 	case RES_NORELOAD:	return "no-reload";
c5d972
+	case RES_NOAAAA:	return "no-aaaa";
c5d972
 				/* XXX nonreentrant */
c5d972
 	default:		sprintf(nbuf, "?0x%lx?", (u_long)option);
c5d972
 				return (nbuf);
c5d972
diff --git a/resolv/res_init.c b/resolv/res_init.c
c5d972
index bb99ddeec4d6d47f..20434bfe147a3fb5 100644
c5d972
--- a/resolv/res_init.c
c5d972
+++ b/resolv/res_init.c
c5d972
@@ -694,7 +694,8 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
c5d972
             { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
c5d972
             { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
c5d972
             { STRnLEN ("no-reload"), 0, RES_NORELOAD },
c5d972
-            { STRnLEN ("use-vc"), 0, RES_USEVC }
c5d972
+            { STRnLEN ("use-vc"), 0, RES_USEVC },
c5d972
+            { STRnLEN ("no-aaaa"), 0, RES_NOAAAA },
c5d972
           };
c5d972
 #define noptions (sizeof (options) / sizeof (options[0]))
c5d972
           for (int i = 0; i < noptions; ++i)
c5d972
diff --git a/resolv/res_query.c b/resolv/res_query.c
c5d972
index ebbe5a6a4ed86abe..d94966a47c3dac90 100644
c5d972
--- a/resolv/res_query.c
c5d972
+++ b/resolv/res_query.c
c5d972
@@ -204,10 +204,26 @@ __res_context_query (struct resolv_context *ctx, const char *name,
c5d972
 			free (buf);
c5d972
 		return (n);
c5d972
 	}
c5d972
-	assert (answerp == NULL || (void *) *answerp == (void *) answer);
c5d972
-	n = __res_context_send (ctx, query1, nquery1, query2, nquery2, answer,
c5d972
-				anslen, answerp, answerp2, nanswerp2, resplen2,
c5d972
-				answerp2_malloced);
c5d972
+
c5d972
+	/* Suppress AAAA lookups if required.  __res_handle_no_aaaa
c5d972
+	   checks RES_NOAAAA first, so avoids parsing the
c5d972
+	   just-generated query packet in most cases.  nss_dns avoids
c5d972
+	   using T_QUERY_A_AND_AAAA in RES_NOAAAA mode, so there is no
c5d972
+	   need to handle it here.  */
c5d972
+	if (type == T_AAAA && __res_handle_no_aaaa (ctx, query1, nquery1,
c5d972
+						    answer, anslen, &n))
c5d972
+	  /* There must be no second query for AAAA queries.  The code
c5d972
+	     below is still needed to translate NODATA responses.  */
c5d972
+	  assert (query2 == NULL);
c5d972
+	else
c5d972
+	  {
c5d972
+	    assert (answerp == NULL || (void *) *answerp == (void *) answer);
c5d972
+	    n = __res_context_send (ctx, query1, nquery1, query2, nquery2,
c5d972
+				    answer, anslen,
c5d972
+				    answerp, answerp2, nanswerp2, resplen2,
c5d972
+				    answerp2_malloced);
c5d972
+	  }
c5d972
+
c5d972
 	if (use_malloc)
c5d972
 		free (buf);
c5d972
 	if (n < 0) {
c5d972
diff --git a/resolv/res_send.c b/resolv/res_send.c
c5d972
index 55e7fa438e7baac1..2e676bff0edf0cdc 100644
c5d972
--- a/resolv/res_send.c
c5d972
+++ b/resolv/res_send.c
c5d972
@@ -550,8 +550,13 @@ context_send_common (struct resolv_context *ctx,
c5d972
       RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
c5d972
       return -1;
c5d972
     }
c5d972
-  int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
c5d972
-				   NULL, NULL, NULL, NULL, NULL);
c5d972
+
c5d972
+  int result;
c5d972
+  if (__res_handle_no_aaaa (ctx, buf, buflen, ans, anssiz, &result))
c5d972
+    return result;
c5d972
+
c5d972
+  result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
c5d972
+			       NULL, NULL, NULL, NULL, NULL);
c5d972
   __resolv_context_put (ctx);
c5d972
   return result;
c5d972
 }
c5d972
diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
c5d972
index 0878f6830f2a08ff..4564f6ba2f7202f5 100644
c5d972
--- a/resolv/resolv-internal.h
c5d972
+++ b/resolv/resolv-internal.h
c5d972
@@ -79,6 +79,14 @@ int __res_context_send (struct resolv_context *, const unsigned char *, int,
c5d972
                         int, unsigned char **, unsigned char **,
c5d972
                         int *, int *, int *) attribute_hidden;
c5d972
 
c5d972
+/* Return true if the query has been handled in RES_NOAAAA mode.  For
c5d972
+   that, RES_NOAAAA must be active, and the question type must be AAAA.
c5d972
+   The caller is expected to return *RESULT as the return value.  */
c5d972
+bool __res_handle_no_aaaa (struct resolv_context *ctx,
c5d972
+                           const unsigned char *buf, int buflen,
c5d972
+                           unsigned char *ans, int anssiz, int *result)
c5d972
+  attribute_hidden;
c5d972
+
c5d972
 /* Internal function similar to res_hostalias.  */
c5d972
 const char *__res_context_hostalias (struct resolv_context *,
c5d972
                                      const char *, char *, size_t);
c5d972
diff --git a/resolv/resolv.h b/resolv/resolv.h
c5d972
index 80a523e5e40982ad..0f7298f395a829d3 100644
c5d972
--- a/resolv/resolv.h
c5d972
+++ b/resolv/resolv.h
c5d972
@@ -135,6 +135,7 @@ struct res_sym {
c5d972
 #define RES_NOTLDQUERY	0x01000000	/* Do not look up unqualified name
c5d972
 					   as a TLD.  */
c5d972
 #define RES_NORELOAD    0x02000000 /* No automatic configuration reload.  */
c5d972
+#define RES_NOAAAA      0x08000000 /* Suppress AAAA queries.  */
c5d972
 
c5d972
 #define RES_DEFAULT	(RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
c5d972
 
c5d972
diff --git a/resolv/tst-resolv-noaaaa.c b/resolv/tst-resolv-noaaaa.c
c5d972
new file mode 100644
c5d972
index 0000000000000000..56b25f88a58ad286
c5d972
--- /dev/null
c5d972
+++ b/resolv/tst-resolv-noaaaa.c
c5d972
@@ -0,0 +1,533 @@
c5d972
+/* Test the RES_NOAAAA resolver option.
c5d972
+   Copyright (C) 2022 Free Software Foundation, Inc.
c5d972
+   This file is part of the GNU C Library.
c5d972
+
c5d972
+   The GNU C Library is free software; you can redistribute it and/or
c5d972
+   modify it under the terms of the GNU Lesser General Public
c5d972
+   License as published by the Free Software Foundation; either
c5d972
+   version 2.1 of the License, or (at your option) any later version.
c5d972
+
c5d972
+   The GNU C Library is distributed in the hope that it will be useful,
c5d972
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
c5d972
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
c5d972
+   Lesser General Public License for more details.
c5d972
+
c5d972
+   You should have received a copy of the GNU Lesser General Public
c5d972
+   License along with the GNU C Library; if not, see
c5d972
+   <https://www.gnu.org/licenses/>.  */
c5d972
+
c5d972
+#include <errno.h>
c5d972
+#include <netdb.h>
c5d972
+#include <resolv.h>
c5d972
+#include <stdlib.h>
c5d972
+#include <support/check.h>
c5d972
+#include <support/check_nss.h>
c5d972
+#include <support/resolv_test.h>
c5d972
+#include <support/support.h>
c5d972
+
c5d972
+/* Used to keep track of the number of queries.  */
c5d972
+static volatile unsigned int queries;
c5d972
+
c5d972
+static void
c5d972
+response (const struct resolv_response_context *ctx,
c5d972
+          struct resolv_response_builder *b,
c5d972
+          const char *qname, uint16_t qclass, uint16_t qtype)
c5d972
+{
c5d972
+  /* Each test should only send one query.  */
c5d972
+  ++queries;
c5d972
+  TEST_COMPARE (queries, 1);
c5d972
+
c5d972
+  /* AAAA queries are supposed to be disabled.  */
c5d972
+  TEST_VERIFY (qtype != T_AAAA);
c5d972
+  TEST_COMPARE (qclass, C_IN);
c5d972
+
c5d972
+  /* The only other query type besides A is PTR.  */
c5d972
+  if (qtype != T_A)
c5d972
+    TEST_COMPARE (qtype, T_PTR);
c5d972
+
c5d972
+  int an, ns, ar;
c5d972
+  char *tail;
c5d972
+  if (sscanf (qname, "an%d.ns%d.ar%d.%ms", &an, &ns, &ar, &tail) != 4)
c5d972
+    FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
c5d972
+  TEST_COMPARE_STRING (tail, "example");
c5d972
+  free (tail);
c5d972
+
c5d972
+  if (an < 0 || ns < 0 || ar < 0)
c5d972
+    {
c5d972
+      struct resolv_response_flags flags = { .rcode = NXDOMAIN, };
c5d972
+      resolv_response_init (b, flags);
c5d972
+      resolv_response_add_question (b, qname, qclass, qtype);
c5d972
+      return;
c5d972
+    }
c5d972
+
c5d972
+  struct resolv_response_flags flags = {};
c5d972
+  resolv_response_init (b, flags);
c5d972
+  resolv_response_add_question (b, qname, qclass, qtype);
c5d972
+
c5d972
+  resolv_response_section (b, ns_s_an);
c5d972
+  for (int i = 0; i < an; ++i)
c5d972
+    {
c5d972
+      resolv_response_open_record (b, qname, qclass, qtype, 60);
c5d972
+      switch (qtype)
c5d972
+        {
c5d972
+        case T_A:
c5d972
+          char ipv4[4] = {192, 0, 2, i + 1};
c5d972
+          resolv_response_add_data (b, &ipv4, sizeof (ipv4));
c5d972
+          break;
c5d972
+
c5d972
+        case T_PTR:
c5d972
+          char *name = xasprintf ("ptr-%d", i);
c5d972
+          resolv_response_add_name (b, name);
c5d972
+          free (name);
c5d972
+          break;
c5d972
+        }
c5d972
+      resolv_response_close_record (b);
c5d972
+    }
c5d972
+
c5d972
+  resolv_response_section (b, ns_s_ns);
c5d972
+  for (int i = 0; i < ns; ++i)
c5d972
+    {
c5d972
+      resolv_response_open_record (b, qname, qclass, T_NS, 60);
c5d972
+      char *name = xasprintf ("ns%d.example.net", i);
c5d972
+      resolv_response_add_name (b, name);
c5d972
+      free (name);
c5d972
+      resolv_response_close_record (b);
c5d972
+    }
c5d972
+
c5d972
+  resolv_response_section (b, ns_s_ar);
c5d972
+  int addr = 1;
c5d972
+  for (int i = 0; i < ns; ++i)
c5d972
+    {
c5d972
+      char *name = xasprintf ("ns%d.example.net", i);
c5d972
+      for (int j = 0; j < ar; ++j)
c5d972
+        {
c5d972
+          resolv_response_open_record (b, name, qclass, T_A, 60);
c5d972
+          char ipv4[4] = {192, 0, 2, addr};
c5d972
+          resolv_response_add_data (b, &ipv4, sizeof (ipv4));
c5d972
+          resolv_response_close_record (b);
c5d972
+
c5d972
+          resolv_response_open_record (b, name, qclass, T_AAAA, 60);
c5d972
+          char ipv6[16]
c5d972
+            = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, addr};
c5d972
+          resolv_response_add_data (b, &ipv6, sizeof (ipv6));
c5d972
+          resolv_response_close_record (b);
c5d972
+
c5d972
+          ++addr;
c5d972
+        }
c5d972
+      free (name);
c5d972
+    }
c5d972
+}
c5d972
+
c5d972
+/* Number of modes.  Lowest bit encodes *n* function vs implicit _res
c5d972
+   argument.  The mode numbers themselves are arbitrary.  */
c5d972
+enum { mode_count = 8 };
c5d972
+
c5d972
+/* res_send-like modes do not perform error translation.  */
c5d972
+enum { first_send_mode = 6 };
c5d972
+
c5d972
+static int
c5d972
+libresolv_query (unsigned int mode, const char *qname, uint16_t qtype,
c5d972
+                 unsigned char *buf, size_t buflen)
c5d972
+{
c5d972
+  int saved_errno = errno;
c5d972
+
c5d972
+  TEST_VERIFY_EXIT (mode < mode_count);
c5d972
+
c5d972
+  switch (mode)
c5d972
+    {
c5d972
+    case 0:
c5d972
+      return res_query (qname, C_IN, qtype, buf, buflen);
c5d972
+    case 1:
c5d972
+      return res_nquery (&_res, qname, C_IN, qtype, buf, buflen);
c5d972
+    case 2:
c5d972
+      return res_search (qname, C_IN, qtype, buf, buflen);
c5d972
+    case 3:
c5d972
+      return res_nsearch (&_res, qname, C_IN, qtype, buf, buflen);
c5d972
+    case 4:
c5d972
+      return res_querydomain (qname, "", C_IN, qtype, buf, buflen);
c5d972
+    case 5:
c5d972
+      return res_nquerydomain (&_res, qname, "", C_IN, qtype, buf, buflen);
c5d972
+    case 6:
c5d972
+      {
c5d972
+        unsigned char querybuf[512];
c5d972
+        int ret = res_mkquery (QUERY, qname, C_IN, qtype,
c5d972
+                               NULL, 0, NULL, querybuf, sizeof (querybuf));
c5d972
+        TEST_VERIFY_EXIT (ret > 0);
c5d972
+        errno = saved_errno;
c5d972
+        return res_send (querybuf, ret, buf, buflen);
c5d972
+      }
c5d972
+    case 7:
c5d972
+      {
c5d972
+        unsigned char querybuf[512];
c5d972
+        int ret = res_nmkquery (&_res, QUERY, qname, C_IN, qtype,
c5d972
+                                NULL, 0, NULL, querybuf, sizeof (querybuf));
c5d972
+        TEST_VERIFY_EXIT (ret > 0);
c5d972
+        errno = saved_errno;
c5d972
+        return res_nsend (&_res, querybuf, ret, buf, buflen);
c5d972
+      }
c5d972
+    }
c5d972
+  __builtin_unreachable ();
c5d972
+}
c5d972
+
c5d972
+static int
c5d972
+do_test (void)
c5d972
+{
c5d972
+  struct resolv_test *obj = resolv_test_start
c5d972
+    ((struct resolv_redirect_config)
c5d972
+     {
c5d972
+       .response_callback = response
c5d972
+     });
c5d972
+
c5d972
+  _res.options |= RES_NOAAAA;
c5d972
+
c5d972
+  check_hostent ("an1.ns2.ar1.example",
c5d972
+                 gethostbyname ("an1.ns2.ar1.example"),
c5d972
+                 "name: an1.ns2.ar1.example\n"
c5d972
+                 "address: 192.0.2.1\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an0.ns2.ar1.example",
c5d972
+                 gethostbyname ("an0.ns2.ar1.example"),
c5d972
+                 "error: NO_ADDRESS\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an-1.ns2.ar1.example",
c5d972
+                 gethostbyname ("an-1.ns2.ar1.example"),
c5d972
+                 "error: HOST_NOT_FOUND\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  check_hostent ("an1.ns2.ar1.example AF_INET",
c5d972
+                 gethostbyname2 ("an1.ns2.ar1.example", AF_INET),
c5d972
+                 "name: an1.ns2.ar1.example\n"
c5d972
+                 "address: 192.0.2.1\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an0.ns2.ar1.example AF_INET",
c5d972
+                 gethostbyname2 ("an0.ns2.ar1.example", AF_INET),
c5d972
+                 "error: NO_ADDRESS\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an-1.ns2.ar1.example AF_INET",
c5d972
+                 gethostbyname2 ("an-1.ns2.ar1.example", AF_INET),
c5d972
+                 "error: HOST_NOT_FOUND\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  check_hostent ("an1.ns2.ar1.example AF_INET6",
c5d972
+                 gethostbyname2 ("an1.ns2.ar1.example", AF_INET6),
c5d972
+                 "error: NO_ADDRESS\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an0.ns2.ar1.example AF_INET6",
c5d972
+                 gethostbyname2 ("an0.ns2.ar1.example", AF_INET6),
c5d972
+                 "error: NO_ADDRESS\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an-1.ns2.ar1.example AF_INET6",
c5d972
+                 gethostbyname2 ("an-1.ns2.ar1.example", AF_INET6),
c5d972
+                 "error: HOST_NOT_FOUND\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  /* Multiple addresses.  */
c5d972
+  check_hostent ("an2.ns0.ar0.example",
c5d972
+                 gethostbyname ("an2.ns0.ar0.example"),
c5d972
+                 "name: an2.ns0.ar0.example\n"
c5d972
+                 "address: 192.0.2.1\n"
c5d972
+                 "address: 192.0.2.2\n");
c5d972
+  queries = 0;
c5d972
+  check_hostent ("an2.ns0.ar0.example AF_INET6",
c5d972
+                 gethostbyname2 ("an2.ns0.ar0.example", AF_INET6),
c5d972
+                 "error: NO_ADDRESS\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  /* getaddrinfo checks with one address.  */
c5d972
+  struct addrinfo *ai;
c5d972
+  int ret;
c5d972
+  ret = getaddrinfo ("an1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an1.ns2.ar1.example (AF_INET)", ai, ret,
c5d972
+                  "address: STREAM/TCP 192.0.2.1 80\n");
c5d972
+  freeaddrinfo (ai);
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET6,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an1.ns2.ar1.example (AF_INET6)", ai, ret,
c5d972
+                  "error: No address associated with hostname\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_UNSPEC,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
c5d972
+                  "address: STREAM/TCP 192.0.2.1 80\n");
c5d972
+  freeaddrinfo (ai);
c5d972
+  queries = 0;
c5d972
+
c5d972
+  /* getaddrinfo checks with three addresses.  */
c5d972
+  ret = getaddrinfo ("an3.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an3.ns2.ar1.example (AF_INET)", ai, ret,
c5d972
+                  "address: STREAM/TCP 192.0.2.1 80\n"
c5d972
+                  "address: STREAM/TCP 192.0.2.2 80\n"
c5d972
+                  "address: STREAM/TCP 192.0.2.3 80\n");
c5d972
+  freeaddrinfo (ai);
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an3.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET6,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an3.ns2.ar1.example (AF_INET6)", ai, ret,
c5d972
+                  "error: No address associated with hostname\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an3.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_UNSPEC,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC)", ai, ret,
c5d972
+                  "address: STREAM/TCP 192.0.2.1 80\n"
c5d972
+                  "address: STREAM/TCP 192.0.2.2 80\n"
c5d972
+                  "address: STREAM/TCP 192.0.2.3 80\n");
c5d972
+  freeaddrinfo (ai);
c5d972
+  queries = 0;
c5d972
+
c5d972
+  /* getaddrinfo checks with no address.  */
c5d972
+  ret = getaddrinfo ("an0.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an0.ns2.ar1.example (AF_INET)", ai, ret,
c5d972
+                  "error: No address associated with hostname\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an0.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET6,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an0.ns2.ar1.example (AF_INET6)", ai, ret,
c5d972
+                  "error: No address associated with hostname\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an0.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_UNSPEC,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
c5d972
+                  "error: No address associated with hostname\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  /* getaddrinfo checks with NXDOMAIN.  */
c5d972
+  ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an-1.ns2.ar1.example (AF_INET)", ai, ret,
c5d972
+                  "error: Name or service not known\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_INET6,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an-1.ns2.ar1.example (AF_INET6)", ai, ret,
c5d972
+                  "error: Name or service not known\n");
c5d972
+  queries = 0;
c5d972
+  ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
c5d972
+                     &(struct addrinfo)
c5d972
+                     {
c5d972
+                       .ai_family = AF_UNSPEC,
c5d972
+                       .ai_socktype = SOCK_STREAM,
c5d972
+                     }, &ai;;
c5d972
+  check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
c5d972
+                  "error: Name or service not known\n");
c5d972
+  queries = 0;
c5d972
+
c5d972
+  for (unsigned int mode = 0; mode < mode_count; ++mode)
c5d972
+    {
c5d972
+      unsigned char *buf;
c5d972
+      int ret;
c5d972
+
c5d972
+      /* Response for A.  */
c5d972
+      buf = malloc (512);
c5d972
+      ret = libresolv_query (mode, "an1.ns2.ar1.example", T_A, buf, 512);
c5d972
+      TEST_VERIFY_EXIT (ret > 0);
c5d972
+      check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
c5d972
+                        "name: an1.ns2.ar1.example\n"
c5d972
+                        "address: 192.0.2.1\n");
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NODATA response for A.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an0.ns2.ar1.example", T_A, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, NO_ADDRESS);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
c5d972
+          check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
c5d972
+                            "name: an0.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NXDOMAIN response for A.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_A, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, HOST_NOT_FOUND);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
c5d972
+          check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
c5d972
+                            "name: an-1.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* Response for PTR.  */
c5d972
+      buf = malloc (512);
c5d972
+      ret = libresolv_query (mode, "an1.ns2.ar1.example", T_PTR, buf, 512);
c5d972
+      TEST_VERIFY_EXIT (ret > 0);
c5d972
+      check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
c5d972
+                        "name: an1.ns2.ar1.example\n"
c5d972
+                        "data: an1.ns2.ar1.example PTR ptr-0\n");
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NODATA response for PTR.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an0.ns2.ar1.example", T_PTR, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, NO_ADDRESS);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
c5d972
+          check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
c5d972
+                            "name: an0.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NXDOMAIN response for PTR.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_PTR, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, HOST_NOT_FOUND);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
c5d972
+          check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
c5d972
+                            "name: an-1.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NODATA response for AAAA.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an1.ns2.ar1.example", T_AAAA, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, NO_ADDRESS);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
c5d972
+          check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
c5d972
+                            "name: an1.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NODATA response for AAAA (original is already NODATA).  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an0.ns2.ar1.example", T_AAAA, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, NO_ADDRESS);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
c5d972
+          check_dns_packet ("an0.ns2.ar1.example A", buf, ret,
c5d972
+                            "name: an0.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+
c5d972
+      /* NXDOMAIN response.  */
c5d972
+      buf = malloc (512);
c5d972
+      errno = 0;
c5d972
+      ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_AAAA, buf, 512);
c5d972
+      if (mode < first_send_mode)
c5d972
+        {
c5d972
+          TEST_COMPARE (ret, -1);
c5d972
+          TEST_COMPARE (errno, 0);
c5d972
+          TEST_COMPARE (h_errno, HOST_NOT_FOUND);
c5d972
+        }
c5d972
+      else
c5d972
+        {
c5d972
+          TEST_VERIFY_EXIT (ret > 0);
c5d972
+          TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
c5d972
+          check_dns_packet ("an-1.ns2.ar1.example A", buf, ret,
c5d972
+                            "name: an-1.ns2.ar1.example\n");
c5d972
+        }
c5d972
+      free (buf);
c5d972
+      queries = 0;
c5d972
+    }
c5d972
+
c5d972
+  resolv_test_end (obj);
c5d972
+
c5d972
+  return 0;
c5d972
+}
c5d972
+
c5d972
+#include <support/test-driver.c>
c5d972
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
c5d972
index a5061e6d4fb98311..7d8758a99e180d97 100644
c5d972
--- a/resolv/tst-resolv-res_init-skeleton.c
c5d972
+++ b/resolv/tst-resolv-res_init-skeleton.c
c5d972
@@ -129,6 +129,7 @@ print_resp (FILE *fp, res_state resp)
c5d972
                            "single-request-reopen");
c5d972
         print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
c5d972
         print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
c5d972
+        print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
c5d972
         fputc ('\n', fp);
c5d972
         if (options != 0)
c5d972
           fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
c5d972
@@ -713,6 +714,15 @@ struct test_case test_cases[] =
c5d972
      "nameserver 192.0.2.1\n"
c5d972
      "; nameserver[0]: [192.0.2.1]:53\n"
c5d972
     },
c5d972
+    {.name = "no-aaaa flag",
c5d972
+     .conf = "options no-aaaa\n"
c5d972
+     "nameserver 192.0.2.1\n",
c5d972
+     .expected = "options no-aaaa\n"
c5d972
+     "search example.com\n"
c5d972
+     "; search[0]: example.com\n"
c5d972
+     "nameserver 192.0.2.1\n"
c5d972
+     "; nameserver[0]: [192.0.2.1]:53\n"
c5d972
+    },
c5d972
     { NULL }
c5d972
   };
c5d972