* [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet
@ 2022-06-07 20:29 Florian Weimer
2022-06-07 20:30 ` [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option Florian Weimer
2022-06-24 15:46 ` [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Carlos O'Donell
0 siblings, 2 replies; 4+ messages in thread
From: Florian Weimer @ 2022-06-07 20:29 UTC (permalink / raw)
To: libc-alpha
It makes sense to include the owner name (LHS) and record type in the
output, so that they can be checked for correctness.
---
v2: New patch.
support/support_format_dns_packet.c | 22 +++++++++++++++-------
support/tst-support_format_dns_packet.c | 4 ++--
2 files changed, 17 insertions(+), 9 deletions(-)
diff --git a/support/support_format_dns_packet.c b/support/support_format_dns_packet.c
index e8b3c125e3..14344bc1bf 100644
--- a/support/support_format_dns_packet.c
+++ b/support/support_format_dns_packet.c
@@ -90,6 +90,17 @@ extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value)
return true;
}
+static void
+extract_name_data (struct in_buffer full, struct in_buffer *rdata,
+ const struct dname *owner, const char *typename, FILE *out)
+{
+ struct dname name;
+ if (extract_name (full, rdata, &name))
+ fprintf (out, "data: %s %s %s\n", owner->name, typename, name.name);
+ else
+ fprintf (out, "error: malformed CNAME/PTR record\n");
+}
+
char *
support_format_dns_packet (const unsigned char *buffer, size_t length)
{
@@ -195,14 +206,11 @@ support_format_dns_packet (const unsigned char *buffer, size_t length)
}
break;
case T_CNAME:
+ extract_name_data (full, &rdata, &rname, "CNAME", mem.out);
+ break;
case T_PTR:
- {
- struct dname name;
- if (extract_name (full, &rdata, &name))
- fprintf (mem.out, "name: %s\n", name.name);
- else
- fprintf (mem.out, "error: malformed CNAME/PTR record\n");
- }
+ extract_name_data (full, &rdata, &rname, "PTR", mem.out);
+ break;
}
}
diff --git a/support/tst-support_format_dns_packet.c b/support/tst-support_format_dns_packet.c
index cb7ff53b87..9839aa767e 100644
--- a/support/tst-support_format_dns_packet.c
+++ b/support/tst-support_format_dns_packet.c
@@ -85,8 +85,8 @@ test_multiple_cnames (void)
"\xc0\x00\x02\x01";
check_packet (packet, sizeof (packet) - 1, __func__,
"name: www.example\n"
- "name: www1.example\n"
- "name: www2.example\n"
+ "data: www.example CNAME www1.example\n"
+ "data: www1.example CNAME www2.example\n"
"address: 192.0.2.1\n");
}
base-commit: 5082a287d5e9a1f9cb98b7c982a708a3684f1d5c
--
2.35.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option
2022-06-07 20:29 [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Florian Weimer
@ 2022-06-07 20:30 ` Florian Weimer
2022-06-24 15:42 ` Carlos O'Donell
2022-06-24 15:46 ` [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Carlos O'Donell
1 sibling, 1 reply; 4+ messages in thread
From: Florian Weimer @ 2022-06-07 20:30 UTC (permalink / raw)
To: libc-alpha
---
v2: Fix typos identified by Carlos. Test PTR records as well.
NEWS | 12 +
resolv/Makefile | 3 +
resolv/nss_dns/dns-host.c | 52 ++-
resolv/res-noaaaa.c | 143 +++++++
resolv/res_debug.c | 1 +
resolv/res_init.c | 1 +
resolv/res_query.c | 24 +-
resolv/res_send.c | 9 +-
resolv/resolv-internal.h | 8 +
resolv/resolv.h | 1 +
resolv/tst-resolv-noaaaa.c | 533 ++++++++++++++++++++++++++
resolv/tst-resolv-res_init-skeleton.c | 10 +
12 files changed, 785 insertions(+), 12 deletions(-)
create mode 100644 resolv/res-noaaaa.c
create mode 100644 resolv/tst-resolv-noaaaa.c
diff --git a/NEWS b/NEWS
index 6a213775e6..1574d1d895 100644
--- a/NEWS
+++ b/NEWS
@@ -29,6 +29,18 @@ Major new features:
memory is carried out in the context of the caller, using the caller's
CPU affinity, and priority with CPU usage accounted to the caller.
+* The “no-aaaa” DNS stub resolver option has been added. System
+ administrators can use it to suppress AAAA queries made by the stub
+ resolver, including AAAA lookups triggered by NSS-based interfaces
+ such as getaddrinfo. Only DNS lookups are affected: IPv6 data in
+ /etc/hosts is still used, getaddrinfo with AI_PASSIVE will still
+ produce IPv6 addresses, and configured IPv6 name servers are still
+ used. To produce correct Name Error (NXDOMAIN) results, AAAA queries
+ are translated to A queries. The new resolver option is intended
+ preliminary for diagnostic purposes, to rule out that AAAA DNS queries
+ have adverse impact. It is incompatible with EDNS0 usage and DNSSEC
+ validation by applications.
+
Deprecated and removed features, and other changes affecting compatibility:
* Support for prelink will be removed in the next release; this includes
diff --git a/resolv/Makefile b/resolv/Makefile
index 438672786f..5b15321f9b 100644
--- a/resolv/Makefile
+++ b/resolv/Makefile
@@ -51,6 +51,7 @@ routines := \
nss_dns_functions \
res-close \
res-name-checking \
+ res-noaaaa \
res-state \
res_context_hostalias \
res_enable_icmp \
@@ -92,6 +93,7 @@ tests += \
tst-resolv-binary \
tst-resolv-edns \
tst-resolv-network \
+ tst-resolv-noaaaa \
tst-resolv-nondecimal \
tst-resolv-res_init-multi \
tst-resolv-search \
@@ -265,6 +267,7 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \
$(shared-thread-library)
$(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
$(shared-thread-library)
+$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
index 913a5cb82f..544cffbecd 100644
--- a/resolv/nss_dns/dns-host.c
+++ b/resolv/nss_dns/dns-host.c
@@ -124,6 +124,14 @@ static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
char *buffer, size_t buflen,
int *errnop, int *h_errnop,
int32_t *ttlp);
+static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1,
+ int anslen1,
+ const char *qname,
+ struct gaih_addrtuple **pat,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop,
+ int32_t *ttlp);
+
static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
const char *name, int af,
@@ -369,17 +377,31 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
int resplen2 = 0;
int ans2p_malloced = 0;
+
int olderr = errno;
- int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
+ int n;
+
+ if ((ctx->resp->options & RES_NOAAAA) == 0)
+ {
+ n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
host_buffer.buf->buf, 2048, &host_buffer.ptr,
&ans2p, &nans2p, &resplen2, &ans2p_malloced);
- if (n >= 0)
- {
- status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
- resplen2, name, pat, buffer, buflen,
- errnop, herrnop, ttlp);
+ if (n >= 0)
+ status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
+ resplen2, name, pat, buffer, buflen,
+ errnop, herrnop, ttlp);
}
else
+ {
+ n = __res_context_search (ctx, name, C_IN, T_A,
+ host_buffer.buf->buf, 2048, NULL,
+ NULL, NULL, NULL, NULL);
+ if (n >= 0)
+ status = gaih_getanswer_noaaaa (host_buffer.buf, n,
+ name, pat, buffer, buflen,
+ errnop, herrnop, ttlp);
+ }
+ if (n < 0)
{
switch (errno)
{
@@ -1387,3 +1409,21 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
return status;
}
+
+/* Variant of gaih_getanswer without a second (AAAA) response. */
+static enum nss_status
+gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname,
+ struct gaih_addrtuple **pat,
+ char *buffer, size_t buflen,
+ int *errnop, int *h_errnop, int32_t *ttlp)
+{
+ int first = 1;
+
+ enum nss_status status = NSS_STATUS_NOTFOUND;
+ if (anslen1 > 0)
+ status = gaih_getanswer_slice (answer1, anslen1, qname,
+ &pat, &buffer, &buflen,
+ errnop, h_errnop, ttlp,
+ &first);
+ return status;
+}
diff --git a/resolv/res-noaaaa.c b/resolv/res-noaaaa.c
new file mode 100644
index 0000000000..a71b34a19d
--- /dev/null
+++ b/resolv/res-noaaaa.c
@@ -0,0 +1,143 @@
+/* Implement suppression of AAAA queries.
+ Copyright (C) 2022 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <resolv.h>
+#include <string.h>
+#include <resolv-internal.h>
+#include <resolv_context.h>
+#include <arpa/nameser.h>
+
+/* Returns true if the question type at P matches EXPECTED, and the
+ class is IN. */
+static bool
+qtype_matches (const unsigned char *p, int expected)
+{
+ /* This assumes that T_A/C_IN constants are less than 256, which
+ they are. */
+ return p[0] == 0 && p[1] == expected && p[2] == 0 && p[3] == C_IN;
+}
+
+/* Handle RES_NOAAAA translation of AAAA queries. To produce a Name
+ Error (NXDOMAIN) repsonse for domain names that do not exist, it is
+ still necessary to send a query. Using question type A is a
+ conservative choice. In the returned answer, it is necessary to
+ switch back the question type to AAAA. */
+bool
+__res_handle_no_aaaa (struct resolv_context *ctx,
+ const unsigned char *buf, int buflen,
+ unsigned char *ans, int anssiz, int *result)
+{
+ /* AAAA mode is not active, or the query looks invalid (will not be
+ able to be parsed). */
+ if ((ctx->resp->options & RES_NOAAAA) == 0
+ || buflen <= sizeof (HEADER))
+ return false;
+
+ /* The replacement A query is produced here. */
+ struct
+ {
+ HEADER header;
+ unsigned char question[NS_MAXCDNAME + 4];
+ } replacement;
+ memcpy (&replacement.header, buf, sizeof (replacement.header));
+
+ if (replacement.header.qr
+ || replacement.header.opcode != 0
+ || replacement.header.rcode != 0
+ || ntohs (replacement.header.qdcount) != 1
+ || ntohs (replacement.header.ancount) != 0
+ || ntohs (replacement.header.nscount) != 0)
+ /* Not a well-formed question. Let the core resolver code produce
+ the proper error. */
+ return false;
+
+ /* Disable EDNS0. */
+ replacement.header.arcount = htons (0);
+
+ /* Extract the QNAME. */
+ int ret = __ns_name_unpack (buf, buf + buflen, buf + sizeof (HEADER),
+ replacement.question, NS_MAXCDNAME);
+ if (ret < 0)
+ /* Format error. */
+ return false;
+
+ /* Compute the end of the question name. */
+ const unsigned char *after_question = buf + sizeof (HEADER) + ret;
+
+ /* Check that we are dealing with an AAAA query. */
+ if (buf + buflen - after_question < 4
+ || !qtype_matches (after_question, T_AAAA))
+ return false;
+
+ /* Find the place to store the type/class data in the replacement
+ query. */
+ after_question = replacement.question;
+ /* This cannot fail because __ns_name_unpack above produced a valid
+ domain name. */
+ (void) __ns_name_skip (&after_question, &replacement.question[NS_MAXCDNAME]);
+ unsigned char *start_of_query = (unsigned char *) &replacement;
+ const unsigned char *end_of_query = after_question + 4;
+
+ /* Produce an A/IN query. */
+ {
+ unsigned char *p = (unsigned char *) after_question;
+ p[0] = 0;
+ p[1] = T_A;
+ p[2] = 0;
+ p[3] = C_IN;
+ }
+
+ /* Clear the output buffer, to avoid reading undefined data when
+ rewriting the result from A to AAAA. */
+ memset (ans, 0, anssiz);
+
+ /* Always perform the message translation, independent of the error
+ code. */
+ ret = __res_context_send (ctx,
+ start_of_query, end_of_query - start_of_query,
+ NULL, 0, ans, anssiz,
+ NULL, NULL, NULL, NULL, NULL);
+
+ /* Patch in the AAAA question type if there is room and the A query
+ type was received. */
+ after_question = ans + sizeof (HEADER);
+ if (__ns_name_skip (&after_question, ans + anssiz) == 0
+ && ans + anssiz - after_question >= 4
+ && qtype_matches (after_question, T_A))
+ {
+ ((unsigned char *) after_question)[1] = T_AAAA;
+
+ /* Create an aligned copy of the header. Hide all data exception
+ the question from the response. Put back the header. There is
+ no need to change the response code. The zero answer count turns
+ a positive response with data into a no-data response. */
+ memcpy (&replacement.header, ans, sizeof (replacement.header));
+ replacement.header.ancount = htons (0);
+ replacement.header.nscount = htons (0);
+ replacement.header.arcount = htons (0);
+ memcpy (ans, &replacement.header, sizeof (replacement.header));
+
+ /* Truncate the reply. */
+ if (ret <= 0)
+ *result = ret;
+ else
+ *result = after_question - ans + 4;
+ }
+
+ return true;
+}
diff --git a/resolv/res_debug.c b/resolv/res_debug.c
index 030df0aa90..b0fe69b1aa 100644
--- a/resolv/res_debug.c
+++ b/resolv/res_debug.c
@@ -613,6 +613,7 @@ p_option(u_long option) {
case RES_NOTLDQUERY: return "no-tld-query";
case RES_NORELOAD: return "no-reload";
case RES_TRUSTAD: return "trust-ad";
+ case RES_NOAAAA: return "no-aaaa";
/* XXX nonreentrant */
default: sprintf(nbuf, "?0x%lx?", (u_long)option);
return (nbuf);
diff --git a/resolv/res_init.c b/resolv/res_init.c
index cbc16ba021..2c0bea658e 100644
--- a/resolv/res_init.c
+++ b/resolv/res_init.c
@@ -695,6 +695,7 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
{ STRnLEN ("no-reload"), 0, RES_NORELOAD },
{ STRnLEN ("use-vc"), 0, RES_USEVC },
{ STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
+ { STRnLEN ("no-aaaa"), 0, RES_NOAAAA },
};
#define noptions (sizeof (options) / sizeof (options[0]))
for (int i = 0; i < noptions; ++i)
diff --git a/resolv/res_query.c b/resolv/res_query.c
index 3b5c604261..049de91b95 100644
--- a/resolv/res_query.c
+++ b/resolv/res_query.c
@@ -204,10 +204,26 @@ __res_context_query (struct resolv_context *ctx, const char *name,
free (buf);
return (n);
}
- assert (answerp == NULL || (void *) *answerp == (void *) answer);
- n = __res_context_send (ctx, query1, nquery1, query2, nquery2, answer,
- anslen, answerp, answerp2, nanswerp2, resplen2,
- answerp2_malloced);
+
+ /* Suppress AAAA lookups if required. __res_handle_no_aaaa
+ checks RES_NOAAAA first, so avoids parsing the
+ just-generated query packet in most cases. nss_dns avoids
+ using T_QUERY_A_AND_AAAA in RES_NOAAAA mode, so there is no
+ need to handle it here. */
+ if (type == T_AAAA && __res_handle_no_aaaa (ctx, query1, nquery1,
+ answer, anslen, &n))
+ /* There must be no second query for AAAA queries. The code
+ below is still needed to translate NODATA responses. */
+ assert (query2 == NULL);
+ else
+ {
+ assert (answerp == NULL || (void *) *answerp == (void *) answer);
+ n = __res_context_send (ctx, query1, nquery1, query2, nquery2,
+ answer, anslen,
+ answerp, answerp2, nanswerp2, resplen2,
+ answerp2_malloced);
+ }
+
if (use_malloc)
free (buf);
if (n < 0) {
diff --git a/resolv/res_send.c b/resolv/res_send.c
index d6c85fd7a2..6a08e729a4 100644
--- a/resolv/res_send.c
+++ b/resolv/res_send.c
@@ -438,8 +438,13 @@ context_send_common (struct resolv_context *ctx,
RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
return -1;
}
- int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
- NULL, NULL, NULL, NULL, NULL);
+
+ int result;
+ if (__res_handle_no_aaaa (ctx, buf, buflen, ans, anssiz, &result))
+ return result;
+
+ result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
+ NULL, NULL, NULL, NULL, NULL);
__resolv_context_put (ctx);
return result;
}
diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
index 9d2e832d68..bb12f474d2 100644
--- a/resolv/resolv-internal.h
+++ b/resolv/resolv-internal.h
@@ -85,6 +85,14 @@ int __res_context_send (struct resolv_context *, const unsigned char *, int,
int *, int *, int *);
libc_hidden_proto (__res_context_send)
+/* Return true if the query has been handled in RES_NOAAAA mode. For
+ that, RES_NOAAAA must be active, and the question type must be AAAA.
+ The caller is expected to return *RESULT as the return value. */
+bool __res_handle_no_aaaa (struct resolv_context *ctx,
+ const unsigned char *buf, int buflen,
+ unsigned char *ans, int anssiz, int *result)
+ attribute_hidden;
+
/* Internal function similar to res_hostalias. */
const char *__res_context_hostalias (struct resolv_context *,
const char *, char *, size_t);
diff --git a/resolv/resolv.h b/resolv/resolv.h
index e7c8d44645..3a79ffea57 100644
--- a/resolv/resolv.h
+++ b/resolv/resolv.h
@@ -132,6 +132,7 @@ struct res_sym {
as a TLD. */
#define RES_NORELOAD 0x02000000 /* No automatic configuration reload. */
#define RES_TRUSTAD 0x04000000 /* Request AD bit, keep it in responses. */
+#define RES_NOAAAA 0x08000000 /* Suppress AAAA queries. */
#define RES_DEFAULT (RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
diff --git a/resolv/tst-resolv-noaaaa.c b/resolv/tst-resolv-noaaaa.c
new file mode 100644
index 0000000000..56b25f88a5
--- /dev/null
+++ b/resolv/tst-resolv-noaaaa.c
@@ -0,0 +1,533 @@
+/* Test the RES_NOAAAA resolver option.
+ Copyright (C) 2022 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <errno.h>
+#include <netdb.h>
+#include <resolv.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* Used to keep track of the number of queries. */
+static volatile unsigned int queries;
+
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ /* Each test should only send one query. */
+ ++queries;
+ TEST_COMPARE (queries, 1);
+
+ /* AAAA queries are supposed to be disabled. */
+ TEST_VERIFY (qtype != T_AAAA);
+ TEST_COMPARE (qclass, C_IN);
+
+ /* The only other query type besides A is PTR. */
+ if (qtype != T_A)
+ TEST_COMPARE (qtype, T_PTR);
+
+ int an, ns, ar;
+ char *tail;
+ if (sscanf (qname, "an%d.ns%d.ar%d.%ms", &an, &ns, &ar, &tail) != 4)
+ FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
+ TEST_COMPARE_STRING (tail, "example");
+ free (tail);
+
+ if (an < 0 || ns < 0 || ar < 0)
+ {
+ struct resolv_response_flags flags = { .rcode = NXDOMAIN, };
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ return;
+ }
+
+ struct resolv_response_flags flags = {};
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+
+ resolv_response_section (b, ns_s_an);
+ for (int i = 0; i < an; ++i)
+ {
+ resolv_response_open_record (b, qname, qclass, qtype, 60);
+ switch (qtype)
+ {
+ case T_A:
+ char ipv4[4] = {192, 0, 2, i + 1};
+ resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+ break;
+
+ case T_PTR:
+ char *name = xasprintf ("ptr-%d", i);
+ resolv_response_add_name (b, name);
+ free (name);
+ break;
+ }
+ resolv_response_close_record (b);
+ }
+
+ resolv_response_section (b, ns_s_ns);
+ for (int i = 0; i < ns; ++i)
+ {
+ resolv_response_open_record (b, qname, qclass, T_NS, 60);
+ char *name = xasprintf ("ns%d.example.net", i);
+ resolv_response_add_name (b, name);
+ free (name);
+ resolv_response_close_record (b);
+ }
+
+ resolv_response_section (b, ns_s_ar);
+ int addr = 1;
+ for (int i = 0; i < ns; ++i)
+ {
+ char *name = xasprintf ("ns%d.example.net", i);
+ for (int j = 0; j < ar; ++j)
+ {
+ resolv_response_open_record (b, name, qclass, T_A, 60);
+ char ipv4[4] = {192, 0, 2, addr};
+ resolv_response_add_data (b, &ipv4, sizeof (ipv4));
+ resolv_response_close_record (b);
+
+ resolv_response_open_record (b, name, qclass, T_AAAA, 60);
+ char ipv6[16]
+ = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, addr};
+ resolv_response_add_data (b, &ipv6, sizeof (ipv6));
+ resolv_response_close_record (b);
+
+ ++addr;
+ }
+ free (name);
+ }
+}
+
+/* Number of modes. Lowest bit encodes *n* function vs implicit _res
+ argument. The mode numbers themselves are arbitrary. */
+enum { mode_count = 8 };
+
+/* res_send-like modes do not perform error translation. */
+enum { first_send_mode = 6 };
+
+static int
+libresolv_query (unsigned int mode, const char *qname, uint16_t qtype,
+ unsigned char *buf, size_t buflen)
+{
+ int saved_errno = errno;
+
+ TEST_VERIFY_EXIT (mode < mode_count);
+
+ switch (mode)
+ {
+ case 0:
+ return res_query (qname, C_IN, qtype, buf, buflen);
+ case 1:
+ return res_nquery (&_res, qname, C_IN, qtype, buf, buflen);
+ case 2:
+ return res_search (qname, C_IN, qtype, buf, buflen);
+ case 3:
+ return res_nsearch (&_res, qname, C_IN, qtype, buf, buflen);
+ case 4:
+ return res_querydomain (qname, "", C_IN, qtype, buf, buflen);
+ case 5:
+ return res_nquerydomain (&_res, qname, "", C_IN, qtype, buf, buflen);
+ case 6:
+ {
+ unsigned char querybuf[512];
+ int ret = res_mkquery (QUERY, qname, C_IN, qtype,
+ NULL, 0, NULL, querybuf, sizeof (querybuf));
+ TEST_VERIFY_EXIT (ret > 0);
+ errno = saved_errno;
+ return res_send (querybuf, ret, buf, buflen);
+ }
+ case 7:
+ {
+ unsigned char querybuf[512];
+ int ret = res_nmkquery (&_res, QUERY, qname, C_IN, qtype,
+ NULL, 0, NULL, querybuf, sizeof (querybuf));
+ TEST_VERIFY_EXIT (ret > 0);
+ errno = saved_errno;
+ return res_nsend (&_res, querybuf, ret, buf, buflen);
+ }
+ }
+ __builtin_unreachable ();
+}
+
+static int
+do_test (void)
+{
+ struct resolv_test *obj = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response
+ });
+
+ _res.options |= RES_NOAAAA;
+
+ check_hostent ("an1.ns2.ar1.example",
+ gethostbyname ("an1.ns2.ar1.example"),
+ "name: an1.ns2.ar1.example\n"
+ "address: 192.0.2.1\n");
+ queries = 0;
+ check_hostent ("an0.ns2.ar1.example",
+ gethostbyname ("an0.ns2.ar1.example"),
+ "error: NO_ADDRESS\n");
+ queries = 0;
+ check_hostent ("an-1.ns2.ar1.example",
+ gethostbyname ("an-1.ns2.ar1.example"),
+ "error: HOST_NOT_FOUND\n");
+ queries = 0;
+
+ check_hostent ("an1.ns2.ar1.example AF_INET",
+ gethostbyname2 ("an1.ns2.ar1.example", AF_INET),
+ "name: an1.ns2.ar1.example\n"
+ "address: 192.0.2.1\n");
+ queries = 0;
+ check_hostent ("an0.ns2.ar1.example AF_INET",
+ gethostbyname2 ("an0.ns2.ar1.example", AF_INET),
+ "error: NO_ADDRESS\n");
+ queries = 0;
+ check_hostent ("an-1.ns2.ar1.example AF_INET",
+ gethostbyname2 ("an-1.ns2.ar1.example", AF_INET),
+ "error: HOST_NOT_FOUND\n");
+ queries = 0;
+
+ check_hostent ("an1.ns2.ar1.example AF_INET6",
+ gethostbyname2 ("an1.ns2.ar1.example", AF_INET6),
+ "error: NO_ADDRESS\n");
+ queries = 0;
+ check_hostent ("an0.ns2.ar1.example AF_INET6",
+ gethostbyname2 ("an0.ns2.ar1.example", AF_INET6),
+ "error: NO_ADDRESS\n");
+ queries = 0;
+ check_hostent ("an-1.ns2.ar1.example AF_INET6",
+ gethostbyname2 ("an-1.ns2.ar1.example", AF_INET6),
+ "error: HOST_NOT_FOUND\n");
+ queries = 0;
+
+ /* Multiple addresses. */
+ check_hostent ("an2.ns0.ar0.example",
+ gethostbyname ("an2.ns0.ar0.example"),
+ "name: an2.ns0.ar0.example\n"
+ "address: 192.0.2.1\n"
+ "address: 192.0.2.2\n");
+ queries = 0;
+ check_hostent ("an2.ns0.ar0.example AF_INET6",
+ gethostbyname2 ("an2.ns0.ar0.example", AF_INET6),
+ "error: NO_ADDRESS\n");
+ queries = 0;
+
+ /* getaddrinfo checks with one address. */
+ struct addrinfo *ai;
+ int ret;
+ ret = getaddrinfo ("an1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an1.ns2.ar1.example (AF_INET)", ai, ret,
+ "address: STREAM/TCP 192.0.2.1 80\n");
+ freeaddrinfo (ai);
+ queries = 0;
+ ret = getaddrinfo ("an1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET6,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an1.ns2.ar1.example (AF_INET6)", ai, ret,
+ "error: No address associated with hostname\n");
+ queries = 0;
+ ret = getaddrinfo ("an1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
+ "address: STREAM/TCP 192.0.2.1 80\n");
+ freeaddrinfo (ai);
+ queries = 0;
+
+ /* getaddrinfo checks with three addresses. */
+ ret = getaddrinfo ("an3.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an3.ns2.ar1.example (AF_INET)", ai, ret,
+ "address: STREAM/TCP 192.0.2.1 80\n"
+ "address: STREAM/TCP 192.0.2.2 80\n"
+ "address: STREAM/TCP 192.0.2.3 80\n");
+ freeaddrinfo (ai);
+ queries = 0;
+ ret = getaddrinfo ("an3.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET6,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an3.ns2.ar1.example (AF_INET6)", ai, ret,
+ "error: No address associated with hostname\n");
+ queries = 0;
+ ret = getaddrinfo ("an3.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC)", ai, ret,
+ "address: STREAM/TCP 192.0.2.1 80\n"
+ "address: STREAM/TCP 192.0.2.2 80\n"
+ "address: STREAM/TCP 192.0.2.3 80\n");
+ freeaddrinfo (ai);
+ queries = 0;
+
+ /* getaddrinfo checks with no address. */
+ ret = getaddrinfo ("an0.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an0.ns2.ar1.example (AF_INET)", ai, ret,
+ "error: No address associated with hostname\n");
+ queries = 0;
+ ret = getaddrinfo ("an0.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET6,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an0.ns2.ar1.example (AF_INET6)", ai, ret,
+ "error: No address associated with hostname\n");
+ queries = 0;
+ ret = getaddrinfo ("an0.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
+ "error: No address associated with hostname\n");
+ queries = 0;
+
+ /* getaddrinfo checks with NXDOMAIN. */
+ ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an-1.ns2.ar1.example (AF_INET)", ai, ret,
+ "error: Name or service not known\n");
+ queries = 0;
+ ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_INET6,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an-1.ns2.ar1.example (AF_INET6)", ai, ret,
+ "error: Name or service not known\n");
+ queries = 0;
+ ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
+ &(struct addrinfo)
+ {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_STREAM,
+ }, &ai);
+ check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
+ "error: Name or service not known\n");
+ queries = 0;
+
+ for (unsigned int mode = 0; mode < mode_count; ++mode)
+ {
+ unsigned char *buf;
+ int ret;
+
+ /* Response for A. */
+ buf = malloc (512);
+ ret = libresolv_query (mode, "an1.ns2.ar1.example", T_A, buf, 512);
+ TEST_VERIFY_EXIT (ret > 0);
+ check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
+ "name: an1.ns2.ar1.example\n"
+ "address: 192.0.2.1\n");
+ free (buf);
+ queries = 0;
+
+ /* NODATA response for A. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an0.ns2.ar1.example", T_A, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, NO_ADDRESS);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, 0);
+ check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
+ "name: an0.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* NXDOMAIN response for A. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_A, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, HOST_NOT_FOUND);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
+ check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
+ "name: an-1.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* Response for PTR. */
+ buf = malloc (512);
+ ret = libresolv_query (mode, "an1.ns2.ar1.example", T_PTR, buf, 512);
+ TEST_VERIFY_EXIT (ret > 0);
+ check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
+ "name: an1.ns2.ar1.example\n"
+ "data: an1.ns2.ar1.example PTR ptr-0\n");
+ free (buf);
+ queries = 0;
+
+ /* NODATA response for PTR. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an0.ns2.ar1.example", T_PTR, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, NO_ADDRESS);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, 0);
+ check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
+ "name: an0.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* NXDOMAIN response for PTR. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_PTR, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, HOST_NOT_FOUND);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
+ check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
+ "name: an-1.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* NODATA response for AAAA. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an1.ns2.ar1.example", T_AAAA, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, NO_ADDRESS);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, 0);
+ check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
+ "name: an1.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* NODATA response for AAAA (original is already NODATA). */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an0.ns2.ar1.example", T_AAAA, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, NO_ADDRESS);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, 0);
+ check_dns_packet ("an0.ns2.ar1.example A", buf, ret,
+ "name: an0.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+
+ /* NXDOMAIN response. */
+ buf = malloc (512);
+ errno = 0;
+ ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_AAAA, buf, 512);
+ if (mode < first_send_mode)
+ {
+ TEST_COMPARE (ret, -1);
+ TEST_COMPARE (errno, 0);
+ TEST_COMPARE (h_errno, HOST_NOT_FOUND);
+ }
+ else
+ {
+ TEST_VERIFY_EXIT (ret > 0);
+ TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
+ check_dns_packet ("an-1.ns2.ar1.example A", buf, ret,
+ "name: an-1.ns2.ar1.example\n");
+ }
+ free (buf);
+ queries = 0;
+ }
+
+ resolv_test_end (obj);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
index cf81d1d162..b5749452ef 100644
--- a/resolv/tst-resolv-res_init-skeleton.c
+++ b/resolv/tst-resolv-res_init-skeleton.c
@@ -128,6 +128,7 @@ print_resp (FILE *fp, res_state resp)
print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
+ print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
fputc ('\n', fp);
if (options != 0)
fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
@@ -721,6 +722,15 @@ struct test_case test_cases[] =
"nameserver 192.0.2.1\n"
"; nameserver[0]: [192.0.2.1]:53\n"
},
+ {.name = "no-aaaa flag",
+ .conf = "options no-aaaa\n"
+ "nameserver 192.0.2.1\n",
+ .expected = "options no-aaaa\n"
+ "search example.com\n"
+ "; search[0]: example.com\n"
+ "nameserver 192.0.2.1\n"
+ "; nameserver[0]: [192.0.2.1]:53\n"
+ },
{ NULL }
};
--
2.35.3
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option
2022-06-07 20:30 ` [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option Florian Weimer
@ 2022-06-24 15:42 ` Carlos O'Donell
0 siblings, 0 replies; 4+ messages in thread
From: Carlos O'Donell @ 2022-06-24 15:42 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 6/7/22 16:30, Florian Weimer via Libc-alpha wrote:
> ---
Two typos, but technically everything looks correct so I'll give
my Reviewed-by, and the typos can be fixed under consensus for
correcting grammatical errors.
LGTM.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> v2: Fix typos identified by Carlos. Test PTR records as well.
> NEWS | 12 +
> resolv/Makefile | 3 +
> resolv/nss_dns/dns-host.c | 52 ++-
> resolv/res-noaaaa.c | 143 +++++++
> resolv/res_debug.c | 1 +
> resolv/res_init.c | 1 +
> resolv/res_query.c | 24 +-
> resolv/res_send.c | 9 +-
> resolv/resolv-internal.h | 8 +
> resolv/resolv.h | 1 +
> resolv/tst-resolv-noaaaa.c | 533 ++++++++++++++++++++++++++
OK. New tests added 483->533 lines.
> resolv/tst-resolv-res_init-skeleton.c | 10 +
> 12 files changed, 785 insertions(+), 12 deletions(-)
> create mode 100644 resolv/res-noaaaa.c
> create mode 100644 resolv/tst-resolv-noaaaa.c
>
> diff --git a/NEWS b/NEWS
> index 6a213775e6..1574d1d895 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -29,6 +29,18 @@ Major new features:
> memory is carried out in the context of the caller, using the caller's
> CPU affinity, and priority with CPU usage accounted to the caller.
>
> +* The “no-aaaa” DNS stub resolver option has been added. System
> + administrators can use it to suppress AAAA queries made by the stub
> + resolver, including AAAA lookups triggered by NSS-based interfaces
> + such as getaddrinfo. Only DNS lookups are affected: IPv6 data in
> + /etc/hosts is still used, getaddrinfo with AI_PASSIVE will still
> + produce IPv6 addresses, and configured IPv6 name servers are still
> + used. To produce correct Name Error (NXDOMAIN) results, AAAA queries
> + are translated to A queries. The new resolver option is intended
> + preliminary for diagnostic purposes, to rule out that AAAA DNS queries
s/preliminary/primarily/g
> + have adverse impact. It is incompatible with EDNS0 usage and DNSSEC
> + validation by applications.
OK. We don't say what the impact the incompatibility has, but that's OK, we just disable EDNS0.
> +
> Deprecated and removed features, and other changes affecting compatibility:
>
> * Support for prelink will be removed in the next release; this includes
> diff --git a/resolv/Makefile b/resolv/Makefile
> index 438672786f..5b15321f9b 100644
> --- a/resolv/Makefile
> +++ b/resolv/Makefile
> @@ -51,6 +51,7 @@ routines := \
> nss_dns_functions \
> res-close \
> res-name-checking \
> + res-noaaaa \
> res-state \
> res_context_hostalias \
> res_enable_icmp \
> @@ -92,6 +93,7 @@ tests += \
> tst-resolv-binary \
> tst-resolv-edns \
> tst-resolv-network \
> + tst-resolv-noaaaa \
> tst-resolv-nondecimal \
> tst-resolv-res_init-multi \
> tst-resolv-search \
> @@ -265,6 +267,7 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \
> $(shared-thread-library)
> $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
> $(shared-thread-library)
> +$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
> $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
> $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
> $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
> diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
> index 913a5cb82f..544cffbecd 100644
> --- a/resolv/nss_dns/dns-host.c
> +++ b/resolv/nss_dns/dns-host.c
> @@ -124,6 +124,14 @@ static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
> char *buffer, size_t buflen,
> int *errnop, int *h_errnop,
> int32_t *ttlp);
> +static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1,
> + int anslen1,
> + const char *qname,
> + struct gaih_addrtuple **pat,
> + char *buffer, size_t buflen,
> + int *errnop, int *h_errnop,
> + int32_t *ttlp);
> +
>
> static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
> const char *name, int af,
> @@ -369,17 +377,31 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
> int resplen2 = 0;
> int ans2p_malloced = 0;
>
> +
> int olderr = errno;
> - int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
> + int n;
> +
> + if ((ctx->resp->options & RES_NOAAAA) == 0)
> + {
> + n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
> host_buffer.buf->buf, 2048, &host_buffer.ptr,
> &ans2p, &nans2p, &resplen2, &ans2p_malloced);
> - if (n >= 0)
> - {
> - status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
> - resplen2, name, pat, buffer, buflen,
> - errnop, herrnop, ttlp);
> + if (n >= 0)
> + status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
> + resplen2, name, pat, buffer, buflen,
> + errnop, herrnop, ttlp);
> }
> else
> + {
> + n = __res_context_search (ctx, name, C_IN, T_A,
> + host_buffer.buf->buf, 2048, NULL,
> + NULL, NULL, NULL, NULL);
> + if (n >= 0)
> + status = gaih_getanswer_noaaaa (host_buffer.buf, n,
> + name, pat, buffer, buflen,
> + errnop, herrnop, ttlp);
> + }
> + if (n < 0)
> {
> switch (errno)
> {
> @@ -1387,3 +1409,21 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
>
> return status;
> }
> +
> +/* Variant of gaih_getanswer without a second (AAAA) response. */
> +static enum nss_status
> +gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname,
> + struct gaih_addrtuple **pat,
> + char *buffer, size_t buflen,
> + int *errnop, int *h_errnop, int32_t *ttlp)
> +{
> + int first = 1;
> +
> + enum nss_status status = NSS_STATUS_NOTFOUND;
> + if (anslen1 > 0)
> + status = gaih_getanswer_slice (answer1, anslen1, qname,
> + &pat, &buffer, &buflen,
> + errnop, h_errnop, ttlp,
> + &first);
> + return status;
> +}
> diff --git a/resolv/res-noaaaa.c b/resolv/res-noaaaa.c
> new file mode 100644
> index 0000000000..a71b34a19d
> --- /dev/null
> +++ b/resolv/res-noaaaa.c
> @@ -0,0 +1,143 @@
> +/* Implement suppression of AAAA queries.
> + Copyright (C) 2022 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <resolv.h>
> +#include <string.h>
> +#include <resolv-internal.h>
> +#include <resolv_context.h>
> +#include <arpa/nameser.h>
> +
> +/* Returns true if the question type at P matches EXPECTED, and the
> + class is IN. */
> +static bool
> +qtype_matches (const unsigned char *p, int expected)
> +{
> + /* This assumes that T_A/C_IN constants are less than 256, which
> + they are. */
> + return p[0] == 0 && p[1] == expected && p[2] == 0 && p[3] == C_IN;
> +}
> +
> +/* Handle RES_NOAAAA translation of AAAA queries. To produce a Name
> + Error (NXDOMAIN) repsonse for domain names that do not exist, it is
> + still necessary to send a query. Using question type A is a
> + conservative choice. In the returned answer, it is necessary to
> + switch back the question type to AAAA. */
> +bool
> +__res_handle_no_aaaa (struct resolv_context *ctx,
> + const unsigned char *buf, int buflen,
> + unsigned char *ans, int anssiz, int *result)
> +{
> + /* AAAA mode is not active, or the query looks invalid (will not be
> + able to be parsed). */
> + if ((ctx->resp->options & RES_NOAAAA) == 0
> + || buflen <= sizeof (HEADER))
> + return false;
> +
> + /* The replacement A query is produced here. */
> + struct
> + {
> + HEADER header;
> + unsigned char question[NS_MAXCDNAME + 4];
> + } replacement;
> + memcpy (&replacement.header, buf, sizeof (replacement.header));
> +
> + if (replacement.header.qr
> + || replacement.header.opcode != 0
> + || replacement.header.rcode != 0
> + || ntohs (replacement.header.qdcount) != 1
> + || ntohs (replacement.header.ancount) != 0
> + || ntohs (replacement.header.nscount) != 0)
> + /* Not a well-formed question. Let the core resolver code produce
> + the proper error. */
> + return false;
> +
> + /* Disable EDNS0. */
> + replacement.header.arcount = htons (0);
> +
> + /* Extract the QNAME. */
> + int ret = __ns_name_unpack (buf, buf + buflen, buf + sizeof (HEADER),
> + replacement.question, NS_MAXCDNAME);
> + if (ret < 0)
> + /* Format error. */
> + return false;
> +
> + /* Compute the end of the question name. */
> + const unsigned char *after_question = buf + sizeof (HEADER) + ret;
> +
> + /* Check that we are dealing with an AAAA query. */
> + if (buf + buflen - after_question < 4
> + || !qtype_matches (after_question, T_AAAA))
> + return false;
> +
> + /* Find the place to store the type/class data in the replacement
> + query. */
> + after_question = replacement.question;
> + /* This cannot fail because __ns_name_unpack above produced a valid
> + domain name. */
> + (void) __ns_name_skip (&after_question, &replacement.question[NS_MAXCDNAME]);
> + unsigned char *start_of_query = (unsigned char *) &replacement;
> + const unsigned char *end_of_query = after_question + 4;
> +
> + /* Produce an A/IN query. */
> + {
> + unsigned char *p = (unsigned char *) after_question;
> + p[0] = 0;
> + p[1] = T_A;
> + p[2] = 0;
> + p[3] = C_IN;
> + }
> +
> + /* Clear the output buffer, to avoid reading undefined data when
> + rewriting the result from A to AAAA. */
> + memset (ans, 0, anssiz);
> +
> + /* Always perform the message translation, independent of the error
> + code. */
> + ret = __res_context_send (ctx,
> + start_of_query, end_of_query - start_of_query,
> + NULL, 0, ans, anssiz,
> + NULL, NULL, NULL, NULL, NULL);
> +
> + /* Patch in the AAAA question type if there is room and the A query
> + type was received. */
> + after_question = ans + sizeof (HEADER);
> + if (__ns_name_skip (&after_question, ans + anssiz) == 0
> + && ans + anssiz - after_question >= 4
> + && qtype_matches (after_question, T_A))
> + {
> + ((unsigned char *) after_question)[1] = T_AAAA;
> +
> + /* Create an aligned copy of the header. Hide all data exception
s/exception/except/g
> + the question from the response. Put back the header. There is
> + no need to change the response code. The zero answer count turns
> + a positive response with data into a no-data response. */
> + memcpy (&replacement.header, ans, sizeof (replacement.header));
> + replacement.header.ancount = htons (0);
> + replacement.header.nscount = htons (0);
> + replacement.header.arcount = htons (0);
> + memcpy (ans, &replacement.header, sizeof (replacement.header));
> +
> + /* Truncate the reply. */
> + if (ret <= 0)
> + *result = ret;
> + else
> + *result = after_question - ans + 4;
> + }
> +
> + return true;
> +}
> diff --git a/resolv/res_debug.c b/resolv/res_debug.c
> index 030df0aa90..b0fe69b1aa 100644
> --- a/resolv/res_debug.c
> +++ b/resolv/res_debug.c
> @@ -613,6 +613,7 @@ p_option(u_long option) {
> case RES_NOTLDQUERY: return "no-tld-query";
> case RES_NORELOAD: return "no-reload";
> case RES_TRUSTAD: return "trust-ad";
> + case RES_NOAAAA: return "no-aaaa";
> /* XXX nonreentrant */
> default: sprintf(nbuf, "?0x%lx?", (u_long)option);
> return (nbuf);
> diff --git a/resolv/res_init.c b/resolv/res_init.c
> index cbc16ba021..2c0bea658e 100644
> --- a/resolv/res_init.c
> +++ b/resolv/res_init.c
> @@ -695,6 +695,7 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
> { STRnLEN ("no-reload"), 0, RES_NORELOAD },
> { STRnLEN ("use-vc"), 0, RES_USEVC },
> { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
> + { STRnLEN ("no-aaaa"), 0, RES_NOAAAA },
> };
> #define noptions (sizeof (options) / sizeof (options[0]))
> for (int i = 0; i < noptions; ++i)
> diff --git a/resolv/res_query.c b/resolv/res_query.c
> index 3b5c604261..049de91b95 100644
> --- a/resolv/res_query.c
> +++ b/resolv/res_query.c
> @@ -204,10 +204,26 @@ __res_context_query (struct resolv_context *ctx, const char *name,
> free (buf);
> return (n);
> }
> - assert (answerp == NULL || (void *) *answerp == (void *) answer);
> - n = __res_context_send (ctx, query1, nquery1, query2, nquery2, answer,
> - anslen, answerp, answerp2, nanswerp2, resplen2,
> - answerp2_malloced);
> +
> + /* Suppress AAAA lookups if required. __res_handle_no_aaaa
> + checks RES_NOAAAA first, so avoids parsing the
> + just-generated query packet in most cases. nss_dns avoids
> + using T_QUERY_A_AND_AAAA in RES_NOAAAA mode, so there is no
> + need to handle it here. */
> + if (type == T_AAAA && __res_handle_no_aaaa (ctx, query1, nquery1,
> + answer, anslen, &n))
> + /* There must be no second query for AAAA queries. The code
> + below is still needed to translate NODATA responses. */
> + assert (query2 == NULL);
> + else
> + {
> + assert (answerp == NULL || (void *) *answerp == (void *) answer);
> + n = __res_context_send (ctx, query1, nquery1, query2, nquery2,
> + answer, anslen,
> + answerp, answerp2, nanswerp2, resplen2,
> + answerp2_malloced);
> + }
> +
> if (use_malloc)
> free (buf);
> if (n < 0) {
> diff --git a/resolv/res_send.c b/resolv/res_send.c
> index d6c85fd7a2..6a08e729a4 100644
> --- a/resolv/res_send.c
> +++ b/resolv/res_send.c
> @@ -438,8 +438,13 @@ context_send_common (struct resolv_context *ctx,
> RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
> return -1;
> }
> - int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
> - NULL, NULL, NULL, NULL, NULL);
> +
> + int result;
> + if (__res_handle_no_aaaa (ctx, buf, buflen, ans, anssiz, &result))
> + return result;
> +
> + result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
> + NULL, NULL, NULL, NULL, NULL);
> __resolv_context_put (ctx);
> return result;
> }
> diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
> index 9d2e832d68..bb12f474d2 100644
> --- a/resolv/resolv-internal.h
> +++ b/resolv/resolv-internal.h
> @@ -85,6 +85,14 @@ int __res_context_send (struct resolv_context *, const unsigned char *, int,
> int *, int *, int *);
> libc_hidden_proto (__res_context_send)
>
> +/* Return true if the query has been handled in RES_NOAAAA mode. For
> + that, RES_NOAAAA must be active, and the question type must be AAAA.
> + The caller is expected to return *RESULT as the return value. */
> +bool __res_handle_no_aaaa (struct resolv_context *ctx,
> + const unsigned char *buf, int buflen,
> + unsigned char *ans, int anssiz, int *result)
> + attribute_hidden;
> +
> /* Internal function similar to res_hostalias. */
> const char *__res_context_hostalias (struct resolv_context *,
> const char *, char *, size_t);
> diff --git a/resolv/resolv.h b/resolv/resolv.h
> index e7c8d44645..3a79ffea57 100644
> --- a/resolv/resolv.h
> +++ b/resolv/resolv.h
> @@ -132,6 +132,7 @@ struct res_sym {
> as a TLD. */
> #define RES_NORELOAD 0x02000000 /* No automatic configuration reload. */
> #define RES_TRUSTAD 0x04000000 /* Request AD bit, keep it in responses. */
> +#define RES_NOAAAA 0x08000000 /* Suppress AAAA queries. */
>
> #define RES_DEFAULT (RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
>
> diff --git a/resolv/tst-resolv-noaaaa.c b/resolv/tst-resolv-noaaaa.c
> new file mode 100644
> index 0000000000..56b25f88a5
> --- /dev/null
> +++ b/resolv/tst-resolv-noaaaa.c
> @@ -0,0 +1,533 @@
> +/* Test the RES_NOAAAA resolver option.
> + Copyright (C) 2022 Free Software Foundation, Inc.
> + This file is part of the GNU C Library.
> +
> + The GNU C Library is free software; you can redistribute it and/or
> + modify it under the terms of the GNU Lesser General Public
> + License as published by the Free Software Foundation; either
> + version 2.1 of the License, or (at your option) any later version.
> +
> + The GNU C Library is distributed in the hope that it will be useful,
> + but WITHOUT ANY WARRANTY; without even the implied warranty of
> + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + Lesser General Public License for more details.
> +
> + You should have received a copy of the GNU Lesser General Public
> + License along with the GNU C Library; if not, see
> + <https://www.gnu.org/licenses/>. */
> +
> +#include <errno.h>
> +#include <netdb.h>
> +#include <resolv.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +#include <support/check_nss.h>
> +#include <support/resolv_test.h>
> +#include <support/support.h>
> +
> +/* Used to keep track of the number of queries. */
> +static volatile unsigned int queries;
> +
> +static void
> +response (const struct resolv_response_context *ctx,
> + struct resolv_response_builder *b,
> + const char *qname, uint16_t qclass, uint16_t qtype)
> +{
> + /* Each test should only send one query. */
> + ++queries;
> + TEST_COMPARE (queries, 1);
> +
> + /* AAAA queries are supposed to be disabled. */
> + TEST_VERIFY (qtype != T_AAAA);
> + TEST_COMPARE (qclass, C_IN);
> +
> + /* The only other query type besides A is PTR. */
> + if (qtype != T_A)
> + TEST_COMPARE (qtype, T_PTR);
> +
> + int an, ns, ar;
> + char *tail;
> + if (sscanf (qname, "an%d.ns%d.ar%d.%ms", &an, &ns, &ar, &tail) != 4)
> + FAIL_EXIT1 ("invalid QNAME: %s\n", qname);
> + TEST_COMPARE_STRING (tail, "example");
> + free (tail);
> +
> + if (an < 0 || ns < 0 || ar < 0)
> + {
> + struct resolv_response_flags flags = { .rcode = NXDOMAIN, };
> + resolv_response_init (b, flags);
> + resolv_response_add_question (b, qname, qclass, qtype);
> + return;
> + }
> +
> + struct resolv_response_flags flags = {};
> + resolv_response_init (b, flags);
> + resolv_response_add_question (b, qname, qclass, qtype);
> +
> + resolv_response_section (b, ns_s_an);
> + for (int i = 0; i < an; ++i)
> + {
> + resolv_response_open_record (b, qname, qclass, qtype, 60);
> + switch (qtype)
> + {
> + case T_A:
> + char ipv4[4] = {192, 0, 2, i + 1};
> + resolv_response_add_data (b, &ipv4, sizeof (ipv4));
> + break;
> +
> + case T_PTR:
> + char *name = xasprintf ("ptr-%d", i);
> + resolv_response_add_name (b, name);
> + free (name);
> + break;
> + }
> + resolv_response_close_record (b);
> + }
> +
> + resolv_response_section (b, ns_s_ns);
> + for (int i = 0; i < ns; ++i)
> + {
> + resolv_response_open_record (b, qname, qclass, T_NS, 60);
> + char *name = xasprintf ("ns%d.example.net", i);
> + resolv_response_add_name (b, name);
> + free (name);
> + resolv_response_close_record (b);
> + }
> +
> + resolv_response_section (b, ns_s_ar);
> + int addr = 1;
> + for (int i = 0; i < ns; ++i)
> + {
> + char *name = xasprintf ("ns%d.example.net", i);
> + for (int j = 0; j < ar; ++j)
> + {
> + resolv_response_open_record (b, name, qclass, T_A, 60);
> + char ipv4[4] = {192, 0, 2, addr};
> + resolv_response_add_data (b, &ipv4, sizeof (ipv4));
> + resolv_response_close_record (b);
> +
> + resolv_response_open_record (b, name, qclass, T_AAAA, 60);
> + char ipv6[16]
> + = {0x20, 0x01, 0xd, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, addr};
> + resolv_response_add_data (b, &ipv6, sizeof (ipv6));
> + resolv_response_close_record (b);
> +
> + ++addr;
> + }
> + free (name);
> + }
> +}
> +
> +/* Number of modes. Lowest bit encodes *n* function vs implicit _res
> + argument. The mode numbers themselves are arbitrary. */
> +enum { mode_count = 8 };
> +
> +/* res_send-like modes do not perform error translation. */
> +enum { first_send_mode = 6 };
> +
> +static int
> +libresolv_query (unsigned int mode, const char *qname, uint16_t qtype,
> + unsigned char *buf, size_t buflen)
> +{
> + int saved_errno = errno;
> +
> + TEST_VERIFY_EXIT (mode < mode_count);
> +
> + switch (mode)
> + {
> + case 0:
> + return res_query (qname, C_IN, qtype, buf, buflen);
> + case 1:
> + return res_nquery (&_res, qname, C_IN, qtype, buf, buflen);
> + case 2:
> + return res_search (qname, C_IN, qtype, buf, buflen);
> + case 3:
> + return res_nsearch (&_res, qname, C_IN, qtype, buf, buflen);
> + case 4:
> + return res_querydomain (qname, "", C_IN, qtype, buf, buflen);
> + case 5:
> + return res_nquerydomain (&_res, qname, "", C_IN, qtype, buf, buflen);
> + case 6:
> + {
> + unsigned char querybuf[512];
> + int ret = res_mkquery (QUERY, qname, C_IN, qtype,
> + NULL, 0, NULL, querybuf, sizeof (querybuf));
> + TEST_VERIFY_EXIT (ret > 0);
> + errno = saved_errno;
> + return res_send (querybuf, ret, buf, buflen);
> + }
> + case 7:
> + {
> + unsigned char querybuf[512];
> + int ret = res_nmkquery (&_res, QUERY, qname, C_IN, qtype,
> + NULL, 0, NULL, querybuf, sizeof (querybuf));
> + TEST_VERIFY_EXIT (ret > 0);
> + errno = saved_errno;
> + return res_nsend (&_res, querybuf, ret, buf, buflen);
> + }
> + }
> + __builtin_unreachable ();
> +}
> +
> +static int
> +do_test (void)
> +{
> + struct resolv_test *obj = resolv_test_start
> + ((struct resolv_redirect_config)
> + {
> + .response_callback = response
> + });
> +
> + _res.options |= RES_NOAAAA;
> +
> + check_hostent ("an1.ns2.ar1.example",
> + gethostbyname ("an1.ns2.ar1.example"),
> + "name: an1.ns2.ar1.example\n"
> + "address: 192.0.2.1\n");
> + queries = 0;
> + check_hostent ("an0.ns2.ar1.example",
> + gethostbyname ("an0.ns2.ar1.example"),
> + "error: NO_ADDRESS\n");
> + queries = 0;
> + check_hostent ("an-1.ns2.ar1.example",
> + gethostbyname ("an-1.ns2.ar1.example"),
> + "error: HOST_NOT_FOUND\n");
> + queries = 0;
> +
> + check_hostent ("an1.ns2.ar1.example AF_INET",
> + gethostbyname2 ("an1.ns2.ar1.example", AF_INET),
> + "name: an1.ns2.ar1.example\n"
> + "address: 192.0.2.1\n");
> + queries = 0;
> + check_hostent ("an0.ns2.ar1.example AF_INET",
> + gethostbyname2 ("an0.ns2.ar1.example", AF_INET),
> + "error: NO_ADDRESS\n");
> + queries = 0;
> + check_hostent ("an-1.ns2.ar1.example AF_INET",
> + gethostbyname2 ("an-1.ns2.ar1.example", AF_INET),
> + "error: HOST_NOT_FOUND\n");
> + queries = 0;
> +
> + check_hostent ("an1.ns2.ar1.example AF_INET6",
> + gethostbyname2 ("an1.ns2.ar1.example", AF_INET6),
> + "error: NO_ADDRESS\n");
> + queries = 0;
> + check_hostent ("an0.ns2.ar1.example AF_INET6",
> + gethostbyname2 ("an0.ns2.ar1.example", AF_INET6),
> + "error: NO_ADDRESS\n");
> + queries = 0;
> + check_hostent ("an-1.ns2.ar1.example AF_INET6",
> + gethostbyname2 ("an-1.ns2.ar1.example", AF_INET6),
> + "error: HOST_NOT_FOUND\n");
> + queries = 0;
> +
> + /* Multiple addresses. */
> + check_hostent ("an2.ns0.ar0.example",
> + gethostbyname ("an2.ns0.ar0.example"),
> + "name: an2.ns0.ar0.example\n"
> + "address: 192.0.2.1\n"
> + "address: 192.0.2.2\n");
> + queries = 0;
> + check_hostent ("an2.ns0.ar0.example AF_INET6",
> + gethostbyname2 ("an2.ns0.ar0.example", AF_INET6),
> + "error: NO_ADDRESS\n");
> + queries = 0;
> +
> + /* getaddrinfo checks with one address. */
> + struct addrinfo *ai;
> + int ret;
> + ret = getaddrinfo ("an1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an1.ns2.ar1.example (AF_INET)", ai, ret,
> + "address: STREAM/TCP 192.0.2.1 80\n");
> + freeaddrinfo (ai);
> + queries = 0;
> + ret = getaddrinfo ("an1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET6,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an1.ns2.ar1.example (AF_INET6)", ai, ret,
> + "error: No address associated with hostname\n");
> + queries = 0;
> + ret = getaddrinfo ("an1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_UNSPEC,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
> + "address: STREAM/TCP 192.0.2.1 80\n");
> + freeaddrinfo (ai);
> + queries = 0;
> +
> + /* getaddrinfo checks with three addresses. */
> + ret = getaddrinfo ("an3.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an3.ns2.ar1.example (AF_INET)", ai, ret,
> + "address: STREAM/TCP 192.0.2.1 80\n"
> + "address: STREAM/TCP 192.0.2.2 80\n"
> + "address: STREAM/TCP 192.0.2.3 80\n");
> + freeaddrinfo (ai);
> + queries = 0;
> + ret = getaddrinfo ("an3.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET6,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an3.ns2.ar1.example (AF_INET6)", ai, ret,
> + "error: No address associated with hostname\n");
> + queries = 0;
> + ret = getaddrinfo ("an3.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_UNSPEC,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an3.ns2.ar1.example (AF_UNSPEC)", ai, ret,
> + "address: STREAM/TCP 192.0.2.1 80\n"
> + "address: STREAM/TCP 192.0.2.2 80\n"
> + "address: STREAM/TCP 192.0.2.3 80\n");
> + freeaddrinfo (ai);
> + queries = 0;
> +
> + /* getaddrinfo checks with no address. */
> + ret = getaddrinfo ("an0.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an0.ns2.ar1.example (AF_INET)", ai, ret,
> + "error: No address associated with hostname\n");
> + queries = 0;
> + ret = getaddrinfo ("an0.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET6,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an0.ns2.ar1.example (AF_INET6)", ai, ret,
> + "error: No address associated with hostname\n");
> + queries = 0;
> + ret = getaddrinfo ("an0.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_UNSPEC,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
> + "error: No address associated with hostname\n");
> + queries = 0;
> +
> + /* getaddrinfo checks with NXDOMAIN. */
> + ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an-1.ns2.ar1.example (AF_INET)", ai, ret,
> + "error: Name or service not known\n");
> + queries = 0;
> + ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_INET6,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an-1.ns2.ar1.example (AF_INET6)", ai, ret,
> + "error: Name or service not known\n");
> + queries = 0;
> + ret = getaddrinfo ("an-1.ns2.ar1.example", "80",
> + &(struct addrinfo)
> + {
> + .ai_family = AF_UNSPEC,
> + .ai_socktype = SOCK_STREAM,
> + }, &ai);
> + check_addrinfo ("an-1.ns2.ar1.example (AF_UNSPEC)", ai, ret,
> + "error: Name or service not known\n");
> + queries = 0;
> +
> + for (unsigned int mode = 0; mode < mode_count; ++mode)
> + {
> + unsigned char *buf;
> + int ret;
> +
> + /* Response for A. */
> + buf = malloc (512);
> + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_A, buf, 512);
> + TEST_VERIFY_EXIT (ret > 0);
> + check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
> + "name: an1.ns2.ar1.example\n"
> + "address: 192.0.2.1\n");
> + free (buf);
> + queries = 0;
> +
> + /* NODATA response for A. */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_A, buf, 512);
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, NO_ADDRESS);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> + check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
> + "name: an0.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> +
> + /* NXDOMAIN response for A. */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_A, buf, 512);
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, HOST_NOT_FOUND);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
> + check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
> + "name: an-1.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> +
> + /* Response for PTR. */
OK. New PTR test.
> + buf = malloc (512);
> + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_PTR, buf, 512);
> + TEST_VERIFY_EXIT (ret > 0);
> + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> + "name: an1.ns2.ar1.example\n"
> + "data: an1.ns2.ar1.example PTR ptr-0\n");
> + free (buf);
> + queries = 0;
OK.
> +
> + /* NODATA response for PTR. */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_PTR, buf, 512);
OK. an0 has no data.
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, NO_ADDRESS);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> + "name: an0.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
OK.
> +
> + /* NXDOMAIN response for PTR. */
OK. New test.
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_PTR, buf, 512);
OK. an-1 does not exist.
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, HOST_NOT_FOUND);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
> + check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> + "name: an-1.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> +
... and the rest of thes tests follow.
> + /* NODATA response for AAAA. */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an1.ns2.ar1.example", T_AAAA, buf, 512);
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, NO_ADDRESS);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> + check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
> + "name: an1.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> +
> + /* NODATA response for AAAA (original is already NODATA). */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an0.ns2.ar1.example", T_AAAA, buf, 512);
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, NO_ADDRESS);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> + check_dns_packet ("an0.ns2.ar1.example A", buf, ret,
> + "name: an0.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> +
> + /* NXDOMAIN response. */
> + buf = malloc (512);
> + errno = 0;
> + ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_AAAA, buf, 512);
> + if (mode < first_send_mode)
> + {
> + TEST_COMPARE (ret, -1);
> + TEST_COMPARE (errno, 0);
> + TEST_COMPARE (h_errno, HOST_NOT_FOUND);
> + }
> + else
> + {
> + TEST_VERIFY_EXIT (ret > 0);
> + TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
> + check_dns_packet ("an-1.ns2.ar1.example A", buf, ret,
> + "name: an-1.ns2.ar1.example\n");
> + }
> + free (buf);
> + queries = 0;
> + }
> +
> + resolv_test_end (obj);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
> index cf81d1d162..b5749452ef 100644
> --- a/resolv/tst-resolv-res_init-skeleton.c
> +++ b/resolv/tst-resolv-res_init-skeleton.c
> @@ -128,6 +128,7 @@ print_resp (FILE *fp, res_state resp)
> print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
> print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
> print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
> + print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
> fputc ('\n', fp);
> if (options != 0)
> fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
> @@ -721,6 +722,15 @@ struct test_case test_cases[] =
> "nameserver 192.0.2.1\n"
> "; nameserver[0]: [192.0.2.1]:53\n"
> },
> + {.name = "no-aaaa flag",
> + .conf = "options no-aaaa\n"
> + "nameserver 192.0.2.1\n",
> + .expected = "options no-aaaa\n"
> + "search example.com\n"
> + "; search[0]: example.com\n"
> + "nameserver 192.0.2.1\n"
> + "; nameserver[0]: [192.0.2.1]:53\n"
> + },
> { NULL }
> };
>
--
Cheers,
Carlos.
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet
2022-06-07 20:29 [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Florian Weimer
2022-06-07 20:30 ` [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option Florian Weimer
@ 2022-06-24 15:46 ` Carlos O'Donell
1 sibling, 0 replies; 4+ messages in thread
From: Carlos O'Donell @ 2022-06-24 15:46 UTC (permalink / raw)
To: Florian Weimer, libc-alpha
On 6/7/22 16:29, Florian Weimer via Libc-alpha wrote:
> It makes sense to include the owner name (LHS) and record type in the
> output, so that they can be checked for correctness.
LGTM.
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
> ---
> v2: New patch.
> support/support_format_dns_packet.c | 22 +++++++++++++++-------
> support/tst-support_format_dns_packet.c | 4 ++--
> 2 files changed, 17 insertions(+), 9 deletions(-)
>
> diff --git a/support/support_format_dns_packet.c b/support/support_format_dns_packet.c
> index e8b3c125e3..14344bc1bf 100644
> --- a/support/support_format_dns_packet.c
> +++ b/support/support_format_dns_packet.c
> @@ -90,6 +90,17 @@ extract_name (struct in_buffer full, struct in_buffer *in, struct dname *value)
> return true;
> }
>
> +static void
> +extract_name_data (struct in_buffer full, struct in_buffer *rdata,
> + const struct dname *owner, const char *typename, FILE *out)
> +{
> + struct dname name;
> + if (extract_name (full, rdata, &name))
OK. extract_name() is just above which uses dn_expand() API.
> + fprintf (out, "data: %s %s %s\n", owner->name, typename, name.name);
> + else
> + fprintf (out, "error: malformed CNAME/PTR record\n");
> +}
> +
> char *
> support_format_dns_packet (const unsigned char *buffer, size_t length)
> {
> @@ -195,14 +206,11 @@ support_format_dns_packet (const unsigned char *buffer, size_t length)
> }
> break;
> case T_CNAME:
> + extract_name_data (full, &rdata, &rname, "CNAME", mem.out);
> + break;
OK. The split here allows you to distinguish in the logs if it was a CNAME or PTR that failed.
This allows checking for exact CNAME or PTR message in the logs.
> case T_PTR:
> - {
> - struct dname name;
> - if (extract_name (full, &rdata, &name))
> - fprintf (mem.out, "name: %s\n", name.name);
> - else
> - fprintf (mem.out, "error: malformed CNAME/PTR record\n");
> - }
> + extract_name_data (full, &rdata, &rname, "PTR", mem.out);
> + break;
> }
> }
>
> diff --git a/support/tst-support_format_dns_packet.c b/support/tst-support_format_dns_packet.c
> index cb7ff53b87..9839aa767e 100644
> --- a/support/tst-support_format_dns_packet.c
> +++ b/support/tst-support_format_dns_packet.c
> @@ -85,8 +85,8 @@ test_multiple_cnames (void)
> "\xc0\x00\x02\x01";
> check_packet (packet, sizeof (packet) - 1, __func__,
> "name: www.example\n"
> - "name: www1.example\n"
> - "name: www2.example\n"
> + "data: www.example CNAME www1.example\n"
> + "data: www1.example CNAME www2.example\n"
OK. Data adjusted.
> "address: 192.0.2.1\n");
> }
>
>
> base-commit: 5082a287d5e9a1f9cb98b7c982a708a3684f1d5c
--
Cheers,
Carlos.
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2022-06-24 15:46 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-06-07 20:29 [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Florian Weimer
2022-06-07 20:30 ` [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option Florian Weimer
2022-06-24 15:42 ` Carlos O'Donell
2022-06-24 15:46 ` [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Carlos O'Donell
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).