public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Carlos O'Donell <carlos@redhat.com>
To: Florian Weimer <fweimer@redhat.com>, libc-alpha@sourceware.org
Subject: Re: [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option
Date: Fri, 24 Jun 2022 11:42:47 -0400	[thread overview]
Message-ID: <cb2936ff-7b7f-2e18-1471-14d62367964e@redhat.com> (raw)
In-Reply-To: <5bd9f5221a5f1357121c4ed83e3ae5b84f51deaa.1654633752.git.fweimer@redhat.com>

On 6/7/22 16:30, Florian Weimer via Libc-alpha wrote:
> ---

Two typos, but technically everything looks correct so I'll give
my Reviewed-by, and the typos can be fixed under consensus for
correcting grammatical errors.

LGTM.

Reviewed-by: Carlos O'Donell <carlos@redhat.com>

> v2: Fix typos identified by Carlos.  Test PTR records as well.
>  NEWS                                  |  12 +
>  resolv/Makefile                       |   3 +
>  resolv/nss_dns/dns-host.c             |  52 ++-
>  resolv/res-noaaaa.c                   | 143 +++++++
>  resolv/res_debug.c                    |   1 +
>  resolv/res_init.c                     |   1 +
>  resolv/res_query.c                    |  24 +-
>  resolv/res_send.c                     |   9 +-
>  resolv/resolv-internal.h              |   8 +
>  resolv/resolv.h                       |   1 +
>  resolv/tst-resolv-noaaaa.c            | 533 ++++++++++++++++++++++++++

OK. New tests added 483->533 lines.

>  resolv/tst-resolv-res_init-skeleton.c |  10 +
>  12 files changed, 785 insertions(+), 12 deletions(-)
>  create mode 100644 resolv/res-noaaaa.c
>  create mode 100644 resolv/tst-resolv-noaaaa.c
> 
> diff --git a/NEWS b/NEWS
> index 6a213775e6..1574d1d895 100644
> --- a/NEWS
> +++ b/NEWS
> @@ -29,6 +29,18 @@ Major new features:
>    memory is carried out in the context of the caller, using the caller's
>    CPU affinity, and priority with CPU usage accounted to the caller.
>  
> +* The “no-aaaa” DNS stub resolver option has been added.  System
> +  administrators can use it to suppress AAAA queries made by the stub
> +  resolver, including AAAA lookups triggered by NSS-based interfaces
> +  such as getaddrinfo.  Only DNS lookups are affected: IPv6 data in
> +  /etc/hosts is still used, getaddrinfo with AI_PASSIVE will still
> +  produce IPv6 addresses, and configured IPv6 name servers are still
> +  used.  To produce correct Name Error (NXDOMAIN) results, AAAA queries
> +  are translated to A queries.  The new resolver option is intended
> +  preliminary for diagnostic purposes, to rule out that AAAA DNS queries

s/preliminary/primarily/g

> +  have adverse impact.  It is incompatible with EDNS0 usage and DNSSEC
> +  validation by applications.

OK. We don't say what the impact the incompatibility has, but that's OK, we just disable EDNS0.

> +
>  Deprecated and removed features, and other changes affecting compatibility:
>  
>  * Support for prelink will be removed in the next release; this includes
> diff --git a/resolv/Makefile b/resolv/Makefile
> index 438672786f..5b15321f9b 100644
> --- a/resolv/Makefile
> +++ b/resolv/Makefile
> @@ -51,6 +51,7 @@ routines := \
>    nss_dns_functions \
>    res-close \
>    res-name-checking \
> +  res-noaaaa \
>    res-state \
>    res_context_hostalias \
>    res_enable_icmp \
> @@ -92,6 +93,7 @@ tests += \
>    tst-resolv-binary \
>    tst-resolv-edns \
>    tst-resolv-network \
> +  tst-resolv-noaaaa \
>    tst-resolv-nondecimal \
>    tst-resolv-res_init-multi \
>    tst-resolv-search \
> @@ -265,6 +267,7 @@ $(objpfx)tst-resolv-res_init-multi: $(objpfx)libresolv.so \
>    $(shared-thread-library)
>  $(objpfx)tst-resolv-res_init-thread: $(objpfx)libresolv.so \
>    $(shared-thread-library)
> +$(objpfx)tst-resolv-noaaaa: $(objpfx)libresolv.so $(shared-thread-library)
>  $(objpfx)tst-resolv-nondecimal: $(objpfx)libresolv.so $(shared-thread-library)
>  $(objpfx)tst-resolv-qtypes: $(objpfx)libresolv.so $(shared-thread-library)
>  $(objpfx)tst-resolv-rotate: $(objpfx)libresolv.so $(shared-thread-library)
> diff --git a/resolv/nss_dns/dns-host.c b/resolv/nss_dns/dns-host.c
> index 913a5cb82f..544cffbecd 100644
> --- a/resolv/nss_dns/dns-host.c
> +++ b/resolv/nss_dns/dns-host.c
> @@ -124,6 +124,14 @@ static enum nss_status gaih_getanswer (const querybuf *answer1, int anslen1,
>  				       char *buffer, size_t buflen,
>  				       int *errnop, int *h_errnop,
>  				       int32_t *ttlp);
> +static enum nss_status gaih_getanswer_noaaaa (const querybuf *answer1,
> +					      int anslen1,
> +					      const char *qname,
> +					      struct gaih_addrtuple **pat,
> +					      char *buffer, size_t buflen,
> +					      int *errnop, int *h_errnop,
> +					      int32_t *ttlp);
> +
>  
>  static enum nss_status gethostbyname3_context (struct resolv_context *ctx,
>  					       const char *name, int af,
> @@ -369,17 +377,31 @@ _nss_dns_gethostbyname4_r (const char *name, struct gaih_addrtuple **pat,
>    int resplen2 = 0;
>    int ans2p_malloced = 0;
>  
> +
>    int olderr = errno;
> -  int n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
> +  int n;
> +
> +  if ((ctx->resp->options & RES_NOAAAA) == 0)
> +    {
> +      n = __res_context_search (ctx, name, C_IN, T_QUERY_A_AND_AAAA,
>  				host_buffer.buf->buf, 2048, &host_buffer.ptr,
>  				&ans2p, &nans2p, &resplen2, &ans2p_malloced);
> -  if (n >= 0)
> -    {
> -      status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
> -			       resplen2, name, pat, buffer, buflen,
> -			       errnop, herrnop, ttlp);
> +      if (n >= 0)
> +	status = gaih_getanswer (host_buffer.buf, n, (const querybuf *) ans2p,
> +				 resplen2, name, pat, buffer, buflen,
> +				 errnop, herrnop, ttlp);
>      }
>    else
> +    {
> +      n = __res_context_search (ctx, name, C_IN, T_A,
> +				host_buffer.buf->buf, 2048, NULL,
> +				NULL, NULL, NULL, NULL);
> +      if (n >= 0)
> +	status = gaih_getanswer_noaaaa (host_buffer.buf, n,
> +					name, pat, buffer, buflen,
> +					errnop, herrnop, ttlp);
> +    }
> +  if (n < 0)
>      {
>        switch (errno)
>  	{
> @@ -1387,3 +1409,21 @@ gaih_getanswer (const querybuf *answer1, int anslen1, const querybuf *answer2,
>  
>    return status;
>  }
> +
> +/* Variant of gaih_getanswer without a second (AAAA) response.  */
> +static enum nss_status
> +gaih_getanswer_noaaaa (const querybuf *answer1, int anslen1, const char *qname,
> +		       struct gaih_addrtuple **pat,
> +		       char *buffer, size_t buflen,
> +		       int *errnop, int *h_errnop, int32_t *ttlp)
> +{
> +  int first = 1;
> +
> +  enum nss_status status = NSS_STATUS_NOTFOUND;
> +  if (anslen1 > 0)
> +    status = gaih_getanswer_slice (answer1, anslen1, qname,
> +				   &pat, &buffer, &buflen,
> +				   errnop, h_errnop, ttlp,
> +				   &first);
> +  return status;
> +}
> diff --git a/resolv/res-noaaaa.c b/resolv/res-noaaaa.c
> new file mode 100644
> index 0000000000..a71b34a19d
> --- /dev/null
> +++ b/resolv/res-noaaaa.c
> @@ -0,0 +1,143 @@
> +/* Implement suppression of AAAA queries.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <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 exception

s/exception/except/g

> +         the question from the response.  Put back the header.  There is
> +         no need to change the response code.  The zero answer count turns
> +         a positive response with data into a no-data response.  */
> +      memcpy (&replacement.header, ans, sizeof (replacement.header));
> +      replacement.header.ancount = htons (0);
> +      replacement.header.nscount = htons (0);
> +      replacement.header.arcount = htons (0);
> +      memcpy (ans, &replacement.header, sizeof (replacement.header));
> +
> +      /* Truncate the reply.  */
> +      if (ret <= 0)
> +        *result = ret;
> +      else
> +        *result = after_question - ans + 4;
> +    }
> +
> +  return true;
> +}
> diff --git a/resolv/res_debug.c b/resolv/res_debug.c
> index 030df0aa90..b0fe69b1aa 100644
> --- a/resolv/res_debug.c
> +++ b/resolv/res_debug.c
> @@ -613,6 +613,7 @@ p_option(u_long option) {
>  	case RES_NOTLDQUERY:	return "no-tld-query";
>  	case RES_NORELOAD:	return "no-reload";
>  	case RES_TRUSTAD:	return "trust-ad";
> +	case RES_NOAAAA:	return "no-aaaa";
>  				/* XXX nonreentrant */
>  	default:		sprintf(nbuf, "?0x%lx?", (u_long)option);
>  				return (nbuf);
> diff --git a/resolv/res_init.c b/resolv/res_init.c
> index cbc16ba021..2c0bea658e 100644
> --- a/resolv/res_init.c
> +++ b/resolv/res_init.c
> @@ -695,6 +695,7 @@ res_setoptions (struct resolv_conf_parser *parser, const char *options)
>              { STRnLEN ("no-reload"), 0, RES_NORELOAD },
>              { STRnLEN ("use-vc"), 0, RES_USEVC },
>              { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
> +            { STRnLEN ("no-aaaa"), 0, RES_NOAAAA },
>            };
>  #define noptions (sizeof (options) / sizeof (options[0]))
>            for (int i = 0; i < noptions; ++i)
> diff --git a/resolv/res_query.c b/resolv/res_query.c
> index 3b5c604261..049de91b95 100644
> --- a/resolv/res_query.c
> +++ b/resolv/res_query.c
> @@ -204,10 +204,26 @@ __res_context_query (struct resolv_context *ctx, const char *name,
>  			free (buf);
>  		return (n);
>  	}
> -	assert (answerp == NULL || (void *) *answerp == (void *) answer);
> -	n = __res_context_send (ctx, query1, nquery1, query2, nquery2, answer,
> -				anslen, answerp, answerp2, nanswerp2, resplen2,
> -				answerp2_malloced);
> +
> +	/* Suppress AAAA lookups if required.  __res_handle_no_aaaa
> +	   checks RES_NOAAAA first, so avoids parsing the
> +	   just-generated query packet in most cases.  nss_dns avoids
> +	   using T_QUERY_A_AND_AAAA in RES_NOAAAA mode, so there is no
> +	   need to handle it here.  */
> +	if (type == T_AAAA && __res_handle_no_aaaa (ctx, query1, nquery1,
> +						    answer, anslen, &n))
> +	  /* There must be no second query for AAAA queries.  The code
> +	     below is still needed to translate NODATA responses.  */
> +	  assert (query2 == NULL);
> +	else
> +	  {
> +	    assert (answerp == NULL || (void *) *answerp == (void *) answer);
> +	    n = __res_context_send (ctx, query1, nquery1, query2, nquery2,
> +				    answer, anslen,
> +				    answerp, answerp2, nanswerp2, resplen2,
> +				    answerp2_malloced);
> +	  }
> +
>  	if (use_malloc)
>  		free (buf);
>  	if (n < 0) {
> diff --git a/resolv/res_send.c b/resolv/res_send.c
> index d6c85fd7a2..6a08e729a4 100644
> --- a/resolv/res_send.c
> +++ b/resolv/res_send.c
> @@ -438,8 +438,13 @@ context_send_common (struct resolv_context *ctx,
>        RES_SET_H_ERRNO (&_res, NETDB_INTERNAL);
>        return -1;
>      }
> -  int result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
> -				   NULL, NULL, NULL, NULL, NULL);
> +
> +  int result;
> +  if (__res_handle_no_aaaa (ctx, buf, buflen, ans, anssiz, &result))
> +    return result;
> +
> +  result = __res_context_send (ctx, buf, buflen, NULL, 0, ans, anssiz,
> +			       NULL, NULL, NULL, NULL, NULL);
>    __resolv_context_put (ctx);
>    return result;
>  }
> diff --git a/resolv/resolv-internal.h b/resolv/resolv-internal.h
> index 9d2e832d68..bb12f474d2 100644
> --- a/resolv/resolv-internal.h
> +++ b/resolv/resolv-internal.h
> @@ -85,6 +85,14 @@ int __res_context_send (struct resolv_context *, const unsigned char *, int,
>                          int *, int *, int *);
>  libc_hidden_proto (__res_context_send)
>  
> +/* Return true if the query has been handled in RES_NOAAAA mode.  For
> +   that, RES_NOAAAA must be active, and the question type must be AAAA.
> +   The caller is expected to return *RESULT as the return value.  */
> +bool __res_handle_no_aaaa (struct resolv_context *ctx,
> +                           const unsigned char *buf, int buflen,
> +                           unsigned char *ans, int anssiz, int *result)
> +  attribute_hidden;
> +
>  /* Internal function similar to res_hostalias.  */
>  const char *__res_context_hostalias (struct resolv_context *,
>                                       const char *, char *, size_t);
> diff --git a/resolv/resolv.h b/resolv/resolv.h
> index e7c8d44645..3a79ffea57 100644
> --- a/resolv/resolv.h
> +++ b/resolv/resolv.h
> @@ -132,6 +132,7 @@ struct res_sym {
>  					   as a TLD.  */
>  #define RES_NORELOAD    0x02000000 /* No automatic configuration reload.  */
>  #define RES_TRUSTAD     0x04000000 /* Request AD bit, keep it in responses.  */
> +#define RES_NOAAAA      0x08000000 /* Suppress AAAA queries.  */
>  
>  #define RES_DEFAULT	(RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
>  
> diff --git a/resolv/tst-resolv-noaaaa.c b/resolv/tst-resolv-noaaaa.c
> new file mode 100644
> index 0000000000..56b25f88a5
> --- /dev/null
> +++ b/resolv/tst-resolv-noaaaa.c
> @@ -0,0 +1,533 @@
> +/* Test the RES_NOAAAA resolver option.
> +   Copyright (C) 2022 Free Software Foundation, Inc.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <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.  */

OK. New PTR test.

> +      buf = malloc (512);
> +      ret = libresolv_query (mode, "an1.ns2.ar1.example", T_PTR, buf, 512);
> +      TEST_VERIFY_EXIT (ret > 0);
> +      check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> +                        "name: an1.ns2.ar1.example\n"
> +                        "data: an1.ns2.ar1.example PTR ptr-0\n");
> +      free (buf);
> +      queries = 0;

OK.

> +
> +      /* NODATA response for PTR.  */
> +      buf = malloc (512);
> +      errno = 0;
> +      ret = libresolv_query (mode, "an0.ns2.ar1.example", T_PTR, buf, 512);

OK. an0 has no data.

> +      if (mode < first_send_mode)
> +        {
> +          TEST_COMPARE (ret, -1);
> +          TEST_COMPARE (errno, 0);
> +          TEST_COMPARE (h_errno, NO_ADDRESS);
> +        }
> +      else
> +        {
> +          TEST_VERIFY_EXIT (ret > 0);
> +          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> +          check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> +                            "name: an0.ns2.ar1.example\n");
> +        }
> +      free (buf);
> +      queries = 0;

OK.

> +
> +      /* NXDOMAIN response for PTR.  */

OK. New test.

> +      buf = malloc (512);
> +      errno = 0;
> +      ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_PTR, buf, 512);

OK. an-1 does not exist.

> +      if (mode < first_send_mode)
> +        {
> +          TEST_COMPARE (ret, -1);
> +          TEST_COMPARE (errno, 0);
> +          TEST_COMPARE (h_errno, HOST_NOT_FOUND);
> +        }
> +      else
> +        {
> +          TEST_VERIFY_EXIT (ret > 0);
> +          TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
> +          check_dns_packet ("an1.ns2.ar1.example PTR", buf, ret,
> +                            "name: an-1.ns2.ar1.example\n");
> +        }
> +      free (buf);
> +      queries = 0;
> +

... and the rest of thes tests follow.

> +      /* NODATA response for AAAA.  */
> +      buf = malloc (512);
> +      errno = 0;
> +      ret = libresolv_query (mode, "an1.ns2.ar1.example", T_AAAA, buf, 512);
> +      if (mode < first_send_mode)
> +        {
> +          TEST_COMPARE (ret, -1);
> +          TEST_COMPARE (errno, 0);
> +          TEST_COMPARE (h_errno, NO_ADDRESS);
> +        }
> +      else
> +        {
> +          TEST_VERIFY_EXIT (ret > 0);
> +          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> +          check_dns_packet ("an1.ns2.ar1.example A", buf, ret,
> +                            "name: an1.ns2.ar1.example\n");
> +        }
> +      free (buf);
> +      queries = 0;
> +
> +      /* NODATA response for AAAA (original is already NODATA).  */
> +      buf = malloc (512);
> +      errno = 0;
> +      ret = libresolv_query (mode, "an0.ns2.ar1.example", T_AAAA, buf, 512);
> +      if (mode < first_send_mode)
> +        {
> +          TEST_COMPARE (ret, -1);
> +          TEST_COMPARE (errno, 0);
> +          TEST_COMPARE (h_errno, NO_ADDRESS);
> +        }
> +      else
> +        {
> +          TEST_VERIFY_EXIT (ret > 0);
> +          TEST_COMPARE (((HEADER *)buf)->rcode, 0);
> +          check_dns_packet ("an0.ns2.ar1.example A", buf, ret,
> +                            "name: an0.ns2.ar1.example\n");
> +        }
> +      free (buf);
> +      queries = 0;
> +
> +      /* NXDOMAIN response.  */
> +      buf = malloc (512);
> +      errno = 0;
> +      ret = libresolv_query (mode, "an-1.ns2.ar1.example", T_AAAA, buf, 512);
> +      if (mode < first_send_mode)
> +        {
> +          TEST_COMPARE (ret, -1);
> +          TEST_COMPARE (errno, 0);
> +          TEST_COMPARE (h_errno, HOST_NOT_FOUND);
> +        }
> +      else
> +        {
> +          TEST_VERIFY_EXIT (ret > 0);
> +          TEST_COMPARE (((HEADER *)buf)->rcode, NXDOMAIN);
> +          check_dns_packet ("an-1.ns2.ar1.example A", buf, ret,
> +                            "name: an-1.ns2.ar1.example\n");
> +        }
> +      free (buf);
> +      queries = 0;
> +    }
> +
> +  resolv_test_end (obj);
> +
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/resolv/tst-resolv-res_init-skeleton.c b/resolv/tst-resolv-res_init-skeleton.c
> index cf81d1d162..b5749452ef 100644
> --- a/resolv/tst-resolv-res_init-skeleton.c
> +++ b/resolv/tst-resolv-res_init-skeleton.c
> @@ -128,6 +128,7 @@ print_resp (FILE *fp, res_state resp)
>          print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
>          print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
>          print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
> +        print_option_flag (fp, &options, RES_NOAAAA, "no-aaaa");
>          fputc ('\n', fp);
>          if (options != 0)
>            fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
> @@ -721,6 +722,15 @@ struct test_case test_cases[] =
>       "nameserver 192.0.2.1\n"
>       "; nameserver[0]: [192.0.2.1]:53\n"
>      },
> +    {.name = "no-aaaa flag",
> +     .conf = "options no-aaaa\n"
> +     "nameserver 192.0.2.1\n",
> +     .expected = "options no-aaaa\n"
> +     "search example.com\n"
> +     "; search[0]: example.com\n"
> +     "nameserver 192.0.2.1\n"
> +     "; nameserver[0]: [192.0.2.1]:53\n"
> +    },
>      { NULL }
>    };
>  


-- 
Cheers,
Carlos.


  reply	other threads:[~2022-06-24 15:42 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-06-07 20:29 [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Florian Weimer
2022-06-07 20:30 ` [PATCH v2 2/2] resolv: Implement no-aaaa stub resolver option Florian Weimer
2022-06-24 15:42   ` Carlos O'Donell [this message]
2022-06-24 15:46 ` [PATCH v2 1/2] support: Change non-address output format of support_format_dns_packet Carlos O'Donell

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cb2936ff-7b7f-2e18-1471-14d62367964e@redhat.com \
    --to=carlos@redhat.com \
    --cc=fweimer@redhat.com \
    --cc=libc-alpha@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).