From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTPS id 0147C384146E for ; Fri, 24 Jun 2022 15:42:51 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 0147C384146E Received: from mail-qv1-f72.google.com (mail-qv1-f72.google.com [209.85.219.72]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id us-mta-511-3Hh3MZDMPWG_bdXscU8kZg-1; Fri, 24 Jun 2022 11:42:50 -0400 X-MC-Unique: 3Hh3MZDMPWG_bdXscU8kZg-1 Received: by mail-qv1-f72.google.com with SMTP id mr11-20020a056214348b00b004705c0cb439so2851153qvb.19 for ; Fri, 24 Jun 2022 08:42:50 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:message-id:date:mime-version:user-agent:subject :content-language:to:references:from:organization:in-reply-to :content-transfer-encoding; bh=9te40DZQqOOlxuHwE7s2xuNc6NORl0Mm1JA7ZbwqxmI=; b=yMbo5NZXkI4MNzoy5WZa/dDIcx/heA1mNUdjjJ2fah7gH36JX2k2CtUNcc1i9YtZeP WZUcFwbFFvl6GjapwSgTluKeHvIgdayy4v470D5Psznwng5BaioyWPzNlwcfILGeaDud gFnC89ShfwsX2I3r8cBH25vW+YVB0t2Sf44KDiPBt3gb4v7MqiLqgwJ5TEyQ9WI2Xeol uIQcippiXcvTnTTf0XfNvg0o1uCkiF19D01Ft68XXEKGZqI/fmj2003qLwq+eHcT7542 li6YihrWNGvOPTwm04uSCNus926/VlCWfua6tlBOZcCy9GFVU64e5onfEqDztSWKLrRv l9Jw== X-Gm-Message-State: AJIora+eLDDMDmPwMKBS84us3Hzxnmqcj3fYc+1bSfFYNAVXNc42ESmL rua2OSq3Tr/ivilC/a8/MG2mKWdG7aTERezhPRSAq3w3Rg21Hh7k+yDqM0RUjkGDEWAi2CLoc75 /OKcaIJPr3mLFzVmv36ux X-Received: by 2002:ac8:7dd2:0:b0:305:c5d8:a0b with SMTP id c18-20020ac87dd2000000b00305c5d80a0bmr13719075qte.568.1656085369253; Fri, 24 Jun 2022 08:42:49 -0700 (PDT) X-Google-Smtp-Source: AGRyM1vI6sGUiTfZ1Ywq8LgicL78Z5gtOcEMuYIQQd7H6xnJAMVtvKtHMFfoVK8hBIoqQC+eZPKgng== X-Received: by 2002:ac8:7dd2:0:b0:305:c5d8:a0b with SMTP id c18-20020ac87dd2000000b00305c5d80a0bmr13719028qte.568.1656085368636; Fri, 24 Jun 2022 08:42:48 -0700 (PDT) Received: from [192.168.0.241] (135-23-175-80.cpe.pppoe.ca. [135.23.175.80]) by smtp.gmail.com with ESMTPSA id a20-20020a05620a16d400b0069fe1dfbeffsm2074088qkn.92.2022.06.24.08.42.47 (version=TLS1_3 cipher=TLS_AES_128_GCM_SHA256 bits=128/128); Fri, 24 Jun 2022 08:42:48 -0700 (PDT) Message-ID: Date: Fri, 24 Jun 2022 11:42:47 -0400 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Thunderbird/91.9.0 Subject: Re: [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option To: Florian Weimer , libc-alpha@sourceware.org References: <8df136914557c843ebc4f05fe3e423a8e610e077.1654633752.git.fweimer@redhat.com> <5bd9f5221a5f1357121c4ed83e3ae5b84f51deaa.1654633752.git.fweimer@redhat.com> From: Carlos O'Donell Organization: Red Hat In-Reply-To: <5bd9f5221a5f1357121c4ed83e3ae5b84f51deaa.1654633752.git.fweimer@redhat.com> X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Content-Language: en-US Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-16.6 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, NICE_REPLY_A, RCVD_IN_DNSWL_LOW, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 24 Jun 2022 15:42:56 -0000 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 > 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 > + . */ > + > +#include > +#include > +#include > +#include > +#include > + > +/* 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 > + . */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +/* 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 > 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.