resolv: Implement no-aaaa stub resolver option

Reviewed-by: Carlos O'Donell <carlos@redhat.com>
This commit is contained in:
Florian Weimer 2022-06-24 18:16:41 +02:00
parent 62a321b12d
commit f282cdbe7f
12 changed files with 785 additions and 12 deletions

12
NEWS
View File

@ -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

View File

@ -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)

View File

@ -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;
}

143
resolv/res-noaaaa.c Normal file
View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

533
resolv/tst-resolv-noaaaa.c Normal file
View File

@ -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>

View File

@ -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 }
};