From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: by sourceware.org (Postfix, from userid 2178) id 14F5C383DBBC; Fri, 24 Jun 2022 16:41:14 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 14F5C383DBBC MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Content-Type: text/plain; charset="utf-8" From: Florian Weimer To: glibc-cvs@sourceware.org Subject: [glibc] resolv: Implement no-aaaa stub resolver option X-Act-Checkin: glibc X-Git-Author: Florian Weimer X-Git-Refname: refs/heads/master X-Git-Oldrev: 62a321b12d0e397af88fa422db65079332f971dc X-Git-Newrev: f282cdbe7f436c75864e5640a409a10485e9abb2 Message-Id: <20220624164114.14F5C383DBBC@sourceware.org> Date: Fri, 24 Jun 2022 16:41:14 +0000 (GMT) X-BeenThere: glibc-cvs@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Glibc-cvs mailing list List-Unsubscribe: , List-Archive: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 24 Jun 2022 16:41:14 -0000 https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=f282cdbe7f436c75864e5640a409a10485e9abb2 commit f282cdbe7f436c75864e5640a409a10485e9abb2 Author: Florian Weimer Date: Fri Jun 24 18:16:41 2022 +0200 resolv: Implement no-aaaa stub resolver option Reviewed-by: Carlos O'Donell Diff: --- 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(-) diff --git a/NEWS b/NEWS index 6a213775e6..ea2ab48f9d 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 + primarily 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..4ba197664a --- /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 except + 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. */ + 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 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 } };