| commit f282cdbe7f436c75864e5640a409a10485e9abb2 |
| Author: Florian Weimer <fweimer@redhat.com> |
| Date: Fri Jun 24 18:16:41 2022 +0200 |
| |
| resolv: Implement no-aaaa stub resolver option |
| |
| Reviewed-by: Carlos O'Donell <carlos@redhat.com> |
| |
| Conflicts: |
| resolv/Makefile |
| (missing partial libresolv integration downstream) |
| resolv/res-noaaaa.c |
| (call ns_name_skip instead of __ns_name_skip (not available |
| downstream) and ns_name_unpack instead of __ns_name_unpack |
| (avoid PLT)) |
| resolv/res_debug.c |
| resolv/res_init.c |
| resolv/resolv.h |
| resolv/tst-resolv-res_init-skeleton.c |
| (missing trust-ad support downstream) |
| |
| diff --git a/resolv/Makefile b/resolv/Makefile |
| index cee5225f8933f245..ab8ad49b5318ad41 100644 |
| |
| |
| @@ -57,6 +57,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 \ |
| @@ -110,7 +111,7 @@ libresolv-routines := res_comp res_debug \ |
| res_data res_mkquery res_query res_send \ |
| inet_net_ntop inet_net_pton inet_neta base64 \ |
| ns_parse ns_name ns_netint ns_ttl ns_print \ |
| - ns_samedomain ns_date res_enable_icmp \ |
| + ns_samedomain ns_date res_enable_icmp res-noaaaa \ |
| compat-hooks compat-gethnamaddr |
| |
| libanl-routines := gai_cancel gai_error gai_misc gai_notify gai_suspend \ |
| @@ -200,6 +201,7 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \ |
| $(shared-thread-library) |
| $(objpfx)tst-resolv-res_init-thread: $(libdl) $(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 99c3b61e1cee4d42..ff0a0b6f7f1f4703 100644 |
| |
| |
| @@ -123,6 +123,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, |
| @@ -367,17 +375,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) |
| { |
| @@ -1386,3 +1408,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 0000000000000000..e2a13cf38a74c160 |
| |
| |
| @@ -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 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 7681ad4639d8a7bc..43b3b1bfe4afdcaf 100644 |
| |
| |
| @@ -615,6 +615,7 @@ p_option(u_long option) { |
| case RES_USE_DNSSEC: return "dnssec"; |
| case RES_NOTLDQUERY: return "no-tld-query"; |
| case RES_NORELOAD: return "no-reload"; |
| + 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 bb99ddeec4d6d47f..20434bfe147a3fb5 100644 |
| |
| |
| @@ -694,7 +694,8 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options) |
| { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY }, |
| { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY }, |
| { STRnLEN ("no-reload"), 0, RES_NORELOAD }, |
| - { STRnLEN ("use-vc"), 0, RES_USEVC } |
| + { STRnLEN ("use-vc"), 0, RES_USEVC }, |
| + { 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 ebbe5a6a4ed86abe..d94966a47c3dac90 100644 |
| |
| |
| @@ -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 55e7fa438e7baac1..2e676bff0edf0cdc 100644 |
| |
| |
| @@ -550,8 +550,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 0878f6830f2a08ff..4564f6ba2f7202f5 100644 |
| |
| |
| @@ -79,6 +79,14 @@ int __res_context_send (struct resolv_context *, const unsigned char *, int, |
| int, unsigned char **, unsigned char **, |
| int *, int *, int *) attribute_hidden; |
| |
| +/* 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 80a523e5e40982ad..0f7298f395a829d3 100644 |
| |
| |
| @@ -135,6 +135,7 @@ struct res_sym { |
| #define RES_NOTLDQUERY 0x01000000 /* Do not look up unqualified name |
| as a TLD. */ |
| #define RES_NORELOAD 0x02000000 /* No automatic configuration reload. */ |
| +#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 0000000000000000..56b25f88a58ad286 |
| |
| |
| @@ -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 a5061e6d4fb98311..7d8758a99e180d97 100644 |
| |
| |
| @@ -129,6 +129,7 @@ print_resp (FILE *fp, res_state resp) |
| "single-request-reopen"); |
| 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_NOAAAA, "no-aaaa"); |
| fputc ('\n', fp); |
| if (options != 0) |
| fprintf (fp, "; error: unresolved option bits: 0x%x\n", options); |
| @@ -713,6 +714,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 } |
| }; |
| |