# from https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=f282cdbe7f436c75864e5640a4;hp=62a321b12d0e397af88fa422db65079332f971dc # https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=bd77dd7e73e3530203be1c52c8a29d08270cb25d;hp=c8fa383f4cec9cf1c0cc8ec97903c09af10286f4 diff -ruN a/resolv/Makefile b/resolv/Makefile --- a/resolv/Makefile 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/Makefile 2023-10-27 11:06:33.257096668 +0900 @@ -28,7 +28,7 @@ sys/bitypes.h routines := herror inet_addr inet_ntop inet_pton nsap_addr res_init \ - res_hconf res_libc res-state res_randomid res-close \ + res_hconf res_libc res-noaaaa res-state res_randomid res-close \ resolv_context resolv_conf tests = tst-aton tst-leaks tst-inet_ntop @@ -56,6 +56,8 @@ tst-resolv-binary \ tst-resolv-edns \ tst-resolv-network \ + tst-resolv-noaaaa \ + tst-resolv-noaaaa-vc \ tst-resolv-nondecimal \ tst-resolv-res_init-multi \ tst-resolv-search \ @@ -190,6 +192,8 @@ $(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-noaaaa-vc: $(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 -ruN a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c --- a/resolv/nss_dns/dns-host.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/nss_dns/dns-host.c 2023-10-27 11:09:59.348051859 +0900 @@ -127,6 +127,14 @@ 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, struct hostent *result, @@ -253,7 +261,7 @@ if (af == AF_INET6 && res_use_inet6 ()) n = __res_context_search (ctx, name, C_IN, T_A, host_buffer.buf->buf, host_buffer.buf != orig_host_buffer - ? MAXPACKET : 1024, &host_buffer.ptr, + ? MAXPACKET : 1024, &alt_dns_packet_buffer, NULL, NULL, NULL, NULL); if (n < 0) @@ -371,17 +379,30 @@ 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) { case ESRCH: @@ -1396,3 +1417,21 @@ 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 -ruN a/resolv/res_debug.c b/resolv/res_debug.c --- a/resolv/res_debug.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/res_debug.c 2023-10-27 11:13:27.250116768 +0900 @@ -613,6 +613,7 @@ 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 -ruN a/resolv/res_init.c b/resolv/res_init.c --- a/resolv/res_init.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/res_init.c 2023-10-27 11:13:55.591362390 +0900 @@ -695,6 +695,7 @@ { 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 -ruN a/resolv/res-noaaaa.c b/resolv/res-noaaaa.c --- a/resolv/res-noaaaa.c 1970-01-01 09:00:00.000000000 +0900 +++ b/resolv/res-noaaaa.c 2023-10-27 11:12:03.348440000 +0900 @@ -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 -ruN a/resolv/resolv.h b/resolv/resolv.h --- a/resolv/resolv.h 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/resolv.h 2023-10-27 11:16:45.449827810 +0900 @@ -132,6 +132,7 @@ 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 -ruN a/resolv/resolv-internal.h b/resolv/resolv-internal.h --- a/resolv/resolv-internal.h 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/resolv-internal.h 2023-10-27 11:16:17.148583947 +0900 @@ -76,6 +76,14 @@ 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 -ruN a/resolv/res_query.c b/resolv/res_query.c --- a/resolv/res_query.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/res_query.c 2023-10-27 11:14:34.531073822 +0900 @@ -204,10 +204,26 @@ 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 -ruN a/resolv/res_send.c b/resolv/res_send.c --- a/resolv/res_send.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/res_send.c 2023-10-27 11:15:23.625231551 +0900 @@ -585,8 +585,13 @@ 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 -ruN a/resolv/tst-resolv-noaaaa.c b/resolv/tst-resolv-noaaaa.c --- a/resolv/tst-resolv-noaaaa.c 1970-01-01 09:00:00.000000000 +0900 +++ b/resolv/tst-resolv-noaaaa.c 2023-10-27 11:18:52.618416975 +0900 @@ -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 -ruN a/resolv/tst-resolv-noaaaa-vc.c b/resolv/tst-resolv-noaaaa-vc.c --- a/resolv/tst-resolv-noaaaa-vc.c 1970-01-01 09:00:00.000000000 +0900 +++ b/resolv/tst-resolv-noaaaa-vc.c 2023-10-27 11:12:13.620886365 +0900 @@ -0,0 +1,129 @@ +/* Test the RES_NOAAAA resolver option with a large response. + Copyright (C) 2022-2023 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 +#include +#include + +/* Used to keep track of the number of queries. */ +static volatile unsigned int queries; + +/* If true, add a large TXT record at the start of the answer section. */ +static volatile bool stuff_txt; + +static void +response (const struct resolv_response_context *ctx, + struct resolv_response_builder *b, + const char *qname, uint16_t qclass, uint16_t qtype) +{ + /* If not using TCP, just force its use. */ + if (!ctx->tcp) + { + struct resolv_response_flags flags = {.tc = true}; + resolv_response_init (b, flags); + resolv_response_add_question (b, qname, qclass, qtype); + return; + } + + /* The test needs to send four queries, the first three are used to + grow the NSS buffer via the ERANGE handshake. */ + ++queries; + TEST_VERIFY (queries <= 4); + + /* AAAA queries are supposed to be disabled. */ + TEST_COMPARE (qtype, T_A); + TEST_COMPARE (qclass, C_IN); + TEST_COMPARE_STRING (qname, "example.com"); + + 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); + + if (stuff_txt) + { + resolv_response_open_record (b, qname, qclass, T_TXT, 60); + int zero = 0; + for (int i = 0; i <= 15000; ++i) + resolv_response_add_data (b, &zero, sizeof (zero)); + resolv_response_close_record (b); + } + + for (int i = 0; i < 200; ++i) + { + resolv_response_open_record (b, qname, qclass, qtype, 60); + char ipv4[4] = {192, 0, 2, i + 1}; + resolv_response_add_data (b, &ipv4, sizeof (ipv4)); + resolv_response_close_record (b); + } +} + +static int +do_test (void) +{ + struct resolv_test *obj = resolv_test_start + ((struct resolv_redirect_config) + { + .response_callback = response + }); + + _res.options |= RES_NOAAAA; + + for (int do_stuff_txt = 0; do_stuff_txt < 2; ++do_stuff_txt) + { + queries = 0; + stuff_txt = do_stuff_txt; + + struct addrinfo *ai = NULL; + int ret; + ret = getaddrinfo ("example.com", "80", + &(struct addrinfo) + { + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }, &ai); + + char *expected_result; + { + struct xmemstream mem; + xopen_memstream (&mem); + for (int i = 0; i < 200; ++i) + fprintf (mem.out, "address: STREAM/TCP 192.0.2.%d 80\n", i + 1); + xfclose_memstream (&mem); + expected_result = mem.buffer; + } + + check_addrinfo ("example.com", ai, ret, expected_result); + + free (expected_result); + freeaddrinfo (ai); + } + + resolv_test_end (obj); + return 0; +} + +#include diff -ruN a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c --- a/resolv/tst-resolv-res_init-skeleton.c 2021-02-02 02:15:33.000000000 +0900 +++ b/resolv/tst-resolv-res_init-skeleton.c 2023-10-27 11:19:53.153077524 +0900 @@ -128,6 +128,7 @@ 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); @@ -719,6 +720,15 @@ "search example.com\n" "; search[0]: example.com\n" "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 }