[review] resolv: Implement trust-ad option for /etc/resolv.conf [BZ #20358]
Commit Message
Change URL: https://gnutoolchain-gerrit.osci.io/r/c/glibc/+/461
......................................................................
resolv: Implement trust-ad option for /etc/resolv.conf [BZ #20358]
This introduces a concept of trusted name servers, for which the
AD bit is passed through to applications. For untrusted name
servers (the default), the AD bit in responses are cleared, to
provide a safe default.
This approach is very similar to the one suggested by Pavel Šimerda
in <https://bugzilla.redhat.com/show_bug.cgi?id=1164339#c15>.
The DNS test framework in support/ is enhanced with support for
setting the AD bit in responses.
Tested on x86_64-linux-gnu.
Change-Id: Ibfe0f7c73ea221c35979842c5c3b6ed486495ccc
---
M NEWS
M resolv/Makefile
M resolv/res_debug.c
M resolv/res_init.c
M resolv/res_mkquery.c
M resolv/res_send.c
M resolv/resolv.h
M resolv/tst-resolv-res_init-skeleton.c
A resolv/tst-resolv-trustad.c
M support/resolv_test.c
M support/resolv_test.h
11 files changed, 253 insertions(+), 1 deletion(-)
Comments
Carlos O'Donell has posted comments on this change.
Change URL: https://gnutoolchain-gerrit.osci.io/r/c/glibc/+/461
......................................................................
Patch Set 2: Code-Review+2
(15 comments)
The solution here is similar to what is documented in the upstream wiki design:
https://sourceware.org/glibc/wiki/DNSSEC
That is we are implementing the ad-flag/RES_AD_FLAG option. I think RES_TRUSTAD is a better name since it captures what the flag really means that you trust the resolvers setup in /etc/resolv.conf.
OK for master!
Reviewed-by: Carlos O'Donell <carlos@redhat.com>
| --- NEWS
| +++ NEWS
| @@ -34,17 +34,26 @@ * The gettimeofday function will no longer report information about a
| will always receive a 'struct timezone' whose tz_minuteswest and
| tz_dsttime fields are zero.
|
| * The function pthread_clockjoin_np has been added, enabling join with a
| terminated thread with a specific clock. It allows waiting against
| CLOCK_MONOTONIC and CLOCK_REALTIME. This function is a GNU extension.
|
| * New locale added: mnw_MM (Mon language spoken in Myanmar).
|
| +* The DNS stub resolver will optionally send the AD (authenticated data) bit
| + in queries if the trust-ad option is set via the options directive in
| + /etc/resolv.conf (or if RES_TRUSTAD is set in _res.options). In this
| + mode, the AD bit, as provided by the name server, is available to
| + applications which call res_search and related functions. In the default
| + mode, the AD bit is not set in queries, and it is automatically cleared in
| + responses, indicating a lack of DNSSEC validation. (Therefore, the name
| + servers and the network path to them are treated as untrusted.)
PS2, Line 50:
OK. Agreed, this is the best case design we could come up with at the
time. Thanks for realizing this implementation.
| +
| Deprecated and removed features, and other changes affecting compatibility:
|
| * The totalorder and totalordermag functions, and the corresponding
| functions for other floating-point types, now take pointer arguments to
| avoid signaling NaNs possibly being converted to quiet NaNs in argument
| passing. This is in accordance with the resolution of Clarification
| Request 25 to TS 18661-1, as applied for C2X. Existing binaries that pass
| floating-point arguments directly will continue to work.
| --- resolv/Makefile
| +++ resolv/Makefile
| @@ -62,18 +62,19 @@ tests += \
| tst-resolv-trailing \
|
| # These tests need libdl.
| ifeq (yes,$(build-shared))
| tests += \
| tst-resolv-ai_idn \
| tst-resolv-ai_idn-latin1 \
| tst-resolv-ai_idn-nolibidn2 \
| tst-resolv-canonname \
| + tst-resolv-trustad \
PS2, Line 71:
OK. Test case added.
|
| # Needs resolv_context.
| tests-internal += \
| tst-resolv-res_init \
| tst-resolv-res_init-thread \
| tst-resolv-res_ninit \
| tst-resolv-threads \
|
| # Used by tst-resolv-ai_idn-nolibidn2 to disable libidn2 (by not
...
| @@ -193,16 +194,17 @@ $(objpfx)tst-resolv-res_init-thread: $(libdl) $(objpfx)libresolv.so \
| $(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)
| $(objpfx)tst-resolv-search: $(objpfx)libresolv.so $(shared-thread-library)
| $(objpfx)tst-resolv-trailing: $(objpfx)libresolv.so $(shared-thread-library)
| $(objpfx)tst-resolv-threads: \
| $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
| $(objpfx)tst-resolv-canonname: \
| $(libdl) $(objpfx)libresolv.so $(shared-thread-library)
| +$(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library)
PS2, Line 203:
OK. Link with libresolv.so.
|
| $(objpfx)tst-ns_name: $(objpfx)libresolv.so
| $(objpfx)tst-ns_name.out: tst-ns_name.data
| $(objpfx)tst-ns_name_compress: $(objpfx)libresolv.so
| $(objpfx)tst-ns_name_pton: $(objpfx)libresolv.so
| $(objpfx)tst-res_hnok: $(objpfx)libresolv.so
| $(objpfx)tst-p_secstodate: $(objpfx)libresolv.so
| --- resolv/res_debug.c
| +++ resolv/res_debug.c
| @@ -606,18 +606,19 @@ p_option(u_long option) {
| case RES_DNSRCH: return "dnsrch";
| case RES_NOALIASES: return "noaliases";
| case RES_ROTATE: return "rotate";
| case RES_USE_EDNS0: return "edns0";
| case RES_SNGLKUP: return "single-request";
| case RES_SNGLKUPREOP: return "single-request-reopen";
| case RES_USE_DNSSEC: return "dnssec";
| case RES_NOTLDQUERY: return "no-tld-query";
| case RES_NORELOAD: return "no-reload";
| + case RES_TRUSTAD: return "trust-ad";
PS2, Line 615:
OK, add new RES_TRUSTAD/trust-ad mnemonic mapping.
| /* XXX nonreentrant */
| default: sprintf(nbuf, "?0x%lx?", (u_long)option);
| return (nbuf);
| }
| }
| libresolv_hidden_def (p_option)
|
| /*
| * Return a mnemonic for a time to live.
| --- resolv/res_init.c
| +++ resolv/res_init.c
| @@ -675,17 +675,18 @@ #define STRnLEN(str) str, sizeof (str) - 1
| { STRnLEN ("rotate"), 0, RES_ROTATE },
| { STRnLEN ("edns0"), 0, RES_USE_EDNS0 },
| { STRnLEN ("single-request-reopen"), 0, RES_SNGLKUPREOP },
| { STRnLEN ("single-request"), 0, RES_SNGLKUP },
| { STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
| { STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
| { STRnLEN ("no-reload"), 0, RES_NORELOAD },
| - { STRnLEN ("use-vc"), 0, RES_USEVC }
| + { STRnLEN ("use-vc"), 0, RES_USEVC },
| + { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
PS2, Line 683:
OK. Parse RES_TRUSTAD for trust-ad.
| };
| #define noptions (sizeof (options) / sizeof (options[0]))
| for (int i = 0; i < noptions; ++i)
| if (strncmp (cp, options[i].str, options[i].len) == 0)
| {
| if (options[i].clear)
| parser->template.options &= options[i].flag;
| else
| parser->template.options |= options[i].flag;
| --- resolv/res_mkquery.c
| +++ resolv/res_mkquery.c
| @@ -113,17 +113,19 @@ __res_context_mkquery (struct resolv_context *ctx, int op, const char *dname,
| return -1;
| memset (buf, 0, HFIXEDSZ);
| hp = (HEADER *) buf;
| /* We randomize the IDs every time. The old code just incremented
| by one after the initial randomization which still predictable if
| the application does multiple requests. */
| hp->id = random_bits ();
| hp->opcode = op;
| + if (ctx->resp->options & RES_TRUSTAD)
| + hp->ad = 1;
PS2, Line 122:
OK, if RES_TRUSTAD is set then set ad to 1.
| hp->rd = (ctx->resp->options & RES_RECURSE) != 0;
| hp->rcode = NOERROR;
| cp = buf + HFIXEDSZ;
| buflen -= HFIXEDSZ;
| dpp = dnptrs;
| *dpp++ = buf;
| *dpp++ = NULL;
| lastdnptr = dnptrs + sizeof dnptrs / sizeof dnptrs[0];
|
| --- resolv/res_send.c
| +++ resolv/res_send.c
| @@ -332,10 +332,19 @@ nameserver_offset (struct __res_state *statp)
| }
| }
|
| +/* Clear the AD bit unless the trust-ad option was specified in the
| + resolver configuration. */
| +static void
| +mask_ad_bit (struct resolv_context *ctx, void *buf)
| +{
| + if (!(ctx->resp->options & RES_TRUSTAD))
| + ((HEADER *) buf)->ad = 0;
PS2, Line 341:
OK, if RES_TRUSTAD is not set then clear ad.
| +}
| +
| /* int
| * res_queriesmatch(buf1, eom1, buf2, eom2)
| * is there a 1:1 mapping of (name,type,class)
| * in (buf1,eom1) and (buf2,eom2)?
| * returns:
| * -1 : format error
| * 0 : not a 1:1 mapping
...
| @@ -519,17 +528,29 @@ __res_context_send (struct resolv_context *ctx,
| goto next_ns;
| if (v_circuit)
| // XXX Check whether both requests failed or
| // XXX whether one has been answered successfully
| goto same_ns;
| }
|
| resplen = n;
|
| + /* Mask the AD bit in both responses unless it is
| + marked trusted. */
| + if (resplen > HFIXEDSZ)
| + {
| + if (ansp != NULL)
| + mask_ad_bit (ctx, *ansp);
| + else
| + mask_ad_bit (ctx, ans);
| + }
| + if (resplen2 != NULL && *resplen2 > HFIXEDSZ)
| + mask_ad_bit (ctx, *ansp2);
PS2, Line 547:
OK, we are in res_send.c:__res_context_send, which is some very
complicated code, but here we check in both possible responses and
potentially clear the ad bit.
| +
| /*
| * If we have temporarily opened a virtual circuit,
| * or if we haven't been asked to keep a socket open,
| * close the socket.
| */
| if ((v_circuit && (statp->options & RES_USEVC) == 0) ||
| (statp->options & RES_STAYOPEN) == 0) {
| __res_iclose(statp, false);
| --- resolv/resolv.h
| +++ resolv/resolv.h
| @@ -125,18 +125,19 @@ #define RES_BLAST \
| __glibc_macro_warning ("RES_BLAST is deprecated") 0x00020000
| #define RES_USE_EDNS0 0x00100000 /* Use EDNS0. */
| #define RES_SNGLKUP 0x00200000 /* one outstanding request at a time */
| #define RES_SNGLKUPREOP 0x00400000 /* -"-, but open new socket for each
| request */
| #define RES_USE_DNSSEC 0x00800000 /* use DNSSEC using OK bit in OPT */
| #define RES_NOTLDQUERY 0x01000000 /* Do not look up unqualified name
| as a TLD. */
| #define RES_NORELOAD 0x02000000 /* No automatic configuration reload. */
| +#define RES_TRUSTAD 0x04000000 /* Request AD bit, keep it in responses. */
PS2, Line 134:
OK, new constant.
|
| #define RES_DEFAULT (RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
|
| /*
| * Resolver "pfcode" values. Used by dig.
| */
| #define RES_PRF_STATS 0x00000001
| #define RES_PRF_UPDATE 0x00000002
| #define RES_PRF_CLASS 0x00000004
| --- resolv/tst-resolv-res_init-skeleton.c
| +++ resolv/tst-resolv-res_init-skeleton.c
| @@ -121,18 +121,19 @@ print_resp (FILE *fp, res_state resp)
| print_option_flag (fp, &options, RES_USEVC, "use-vc");
| print_option_flag (fp, &options, RES_ROTATE, "rotate");
| print_option_flag (fp, &options, RES_USE_EDNS0, "edns0");
| print_option_flag (fp, &options, RES_SNGLKUP,
| "single-request");
| print_option_flag (fp, &options, RES_SNGLKUPREOP,
| "single-request-reopen");
| print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
| print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
| + print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
PS2, Line 130:
OK, print the new constant.
| fputc ('\n', fp);
| if (options != 0)
| fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
| }
| }
|
| /* The search and domain directives. */
| if (resp->dnsrch[0] != NULL)
| {
...
| @@ -705,18 +706,27 @@ #define D63 "this-domain-name-is-as-long-as-the-previous-name--63-characters"
| .expected = "search example.com " H63 "." D63 ".example.org\n"
| "; search[0]: example.com\n"
| "; search[1]: " H63 "." D63 ".example.org\n"
| "; search[2]: " H63 "." D63 ".example.net\n"
| #undef H63
| #undef D63
| "nameserver 192.0.2.1\n"
| "; nameserver[0]: [192.0.2.1]:53\n"
| },
| + {.name = "trust-ad flag",
| + .conf = "options trust-ad\n"
| + "nameserver 192.0.2.1\n",
| + .expected = "options trust-ad\n"
| + "search example.com\n"
| + "; search[0]: example.com\n"
| + "nameserver 192.0.2.1\n"
| + "; nameserver[0]: [192.0.2.1]:53\n"
| + },
PS2, Line 723:
OK, in this test we set options trust-ad.
| { NULL }
| };
|
| /* Run the indicated test case. This function assumes that the chroot
| contents has already been set up. */
| static void
| test_file_contents (const struct test_case *t)
| {
| #if TEST_THREAD
| --- /dev/null
| +++ resolv/tst-resolv-trustad.c
| @@ -1,0 +87,28 @@ do_test (void)
| + struct resolv_test *aux = resolv_test_start
| + ((struct resolv_redirect_config)
| + {
| + .response_callback = response,
| + });
| +
| + /* By default, the resolver is not trusted, and the AD bit is
| + cleared. */
| +
| + static const unsigned char hand_crafted_query[] =
| + {
| + 10, 11, /* Transaction ID. */
| + 1, 0x20, /* Query with RD, AD flags. */
| + 0, 1, /* One question. */
| + 0, 0, 0, 0, 0, 0, /* The other sections are empty. */
| + 3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0,
| + 0, T_A, /* A query. */
| + 0, 1, /* Class IN. */
| + };
PS2, Line 105:
OK, default test.
| +
| + ++response_number;
| + response_ad_bit = false;
| +
| + unsigned char buffer[512];
| + memset (buffer, 255, sizeof (buffer));
| + query_ad_bit = true;
| + int ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
| + buffer, sizeof (buffer));
...
| @@ -1,0 +170,19 @@ do_test (void)
| + check_answer (buffer, ret, false);
| +
| + response_ad_bit = true;
| +
| + ++response_number;
| + memset (buffer, 0, sizeof (buffer));
| + ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
| + buffer, sizeof (buffer));
| + TEST_VERIFY (ret > 0);
| + check_answer (buffer, ret, true);
PS2, Line 179:
OK, here we expect an ad enabled answer.
| +
| + ++response_number;
| + memset (buffer, 0, sizeof (buffer));
| + ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
| + TEST_VERIFY (ret > 0);
| + check_answer (buffer, ret, true);
| +
| + /* AD bit set in generated queries. */
| + memset (buffer, 0, sizeof (buffer));
| --- support/resolv_test.c
| +++ support/resolv_test.c
| @@ -177,17 +177,19 @@ resolv_response_init (struct resolv_response_builder *b,
| b->buffer[1] = b->query_buffer[1];
|
| /* Initialize the flags. */
| b->buffer[2] = 0x80; /* Mark as response. */
| b->buffer[2] |= b->query_buffer[2] & 0x01; /* Copy the RD bit. */
| if (flags.tc)
| b->buffer[2] |= 0x02;
| b->buffer[3] = 0x80 | flags.rcode; /* Always set RA. */
| + if (flags.ad)
| + b->buffer[3] |= 0x20;
PS2, Line 186:
OK, set bit.
|
| /* Fill in the initial section count values. */
| b->buffer[4] = flags.qdcount >> 8;
| b->buffer[5] = flags.qdcount;
| b->buffer[6] = flags.ancount >> 8;
| b->buffer[7] = flags.ancount;
| b->buffer[8] = flags.nscount >> 8;
| b->buffer[9] = flags.nscount;
| b->buffer[10] = flags.adcount >> 8;
| --- support/resolv_test.h
| +++ support/resolv_test.h
| @@ -129,16 +129,19 @@ /* Special settings for constructing responses from the callback. */
| struct resolv_response_flags
| {
| /* 4-bit response code to incorporate into the response. */
| unsigned char rcode;
|
| /* If true, the TC (truncation) flag will be set. */
| bool tc;
|
| + /* If true, the AD (authenticated data) flag will be set. */
| + bool ad;
PS2, Line 138:
OK.
| +
| /* Initial section count values. Can be used to artificially
| increase the counts, for malformed packet testing.*/
| unsigned short qdcount;
| unsigned short ancount;
| unsigned short nscount;
| unsigned short adcount;
| };
|
@@ -21,6 +21,15 @@
18661-1:2014 and TS 18661-3:2015 as amended by the resolution of
Clarification Request 13 to TS 18661-3.
+* The DNS stub resolver will optionally send the AD (authenticated data) bit
+ in queries if the trust-ad option is set via the options directive in
+ /etc/resolv.conf (or if RES_TRUSTAD is set in _res.options). In this
+ mode, the AD bit, as provided by the name server, is available to
+ applications which call res_search and related functions. In the default
+ mode, the AD bit is not set in queries, and it is automatically cleared in
+ responses, indicating a lack of DNSSEC validation. (Therefore, the name
+ servers and the network path to them are treated as untrusted.)
+
Deprecated and removed features, and other changes affecting compatibility:
* The totalorder and totalordermag functions, and the corresponding
@@ -68,6 +68,7 @@
tst-resolv-ai_idn-latin1 \
tst-resolv-ai_idn-nolibidn2 \
tst-resolv-canonname \
+ tst-resolv-trustad \
# Needs resolv_context.
tests-internal += \
@@ -199,6 +200,7 @@
$(libdl) $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-resolv-canonname: \
$(libdl) $(objpfx)libresolv.so $(shared-thread-library)
+$(objpfx)tst-resolv-trustad: $(objpfx)libresolv.so $(shared-thread-library)
$(objpfx)tst-ns_name: $(objpfx)libresolv.so
$(objpfx)tst-ns_name.out: tst-ns_name.data
@@ -612,6 +612,7 @@
case RES_USE_DNSSEC: return "dnssec";
case RES_NOTLDQUERY: return "no-tld-query";
case RES_NORELOAD: return "no-reload";
+ case RES_TRUSTAD: return "trust-ad";
/* XXX nonreentrant */
default: sprintf(nbuf, "?0x%lx?", (u_long)option);
return (nbuf);
@@ -679,7 +679,8 @@
{ STRnLEN ("no_tld_query"), 0, RES_NOTLDQUERY },
{ STRnLEN ("no-tld-query"), 0, RES_NOTLDQUERY },
{ STRnLEN ("no-reload"), 0, RES_NORELOAD },
- { STRnLEN ("use-vc"), 0, RES_USEVC }
+ { STRnLEN ("use-vc"), 0, RES_USEVC },
+ { STRnLEN ("trust-ad"), 0, RES_TRUSTAD },
};
#define noptions (sizeof (options) / sizeof (options[0]))
for (int i = 0; i < noptions; ++i)
@@ -118,6 +118,8 @@
the application does multiple requests. */
hp->id = random_bits ();
hp->opcode = op;
+ if (ctx->resp->options & RES_TRUSTAD)
+ hp->ad = 1;
hp->rd = (ctx->resp->options & RES_RECURSE) != 0;
hp->rcode = NOERROR;
cp = buf + HFIXEDSZ;
@@ -337,6 +337,15 @@
}
}
+/* Clear the AD bit unless the trust-ad option was specified in the
+ resolver configuration. */
+static void
+mask_ad_bit (struct resolv_context *ctx, void *buf)
+{
+ if (!(ctx->resp->options & RES_TRUSTAD))
+ ((HEADER *) buf)->ad = 0;
+}
+
/* int
* res_queriesmatch(buf1, eom1, buf2, eom2)
* is there a 1:1 mapping of (name,type,class)
@@ -530,6 +539,18 @@
resplen = n;
+ /* Mask the AD bit in both responses unless it is
+ marked trusted. */
+ if (resplen > HFIXEDSZ)
+ {
+ if (ansp != NULL)
+ mask_ad_bit (ctx, *ansp);
+ else
+ mask_ad_bit (ctx, ans);
+ }
+ if (resplen2 != NULL && *resplen2 > HFIXEDSZ)
+ mask_ad_bit (ctx, *ansp2);
+
/*
* If we have temporarily opened a virtual circuit,
* or if we haven't been asked to keep a socket open,
@@ -131,6 +131,7 @@
#define RES_NOTLDQUERY 0x01000000 /* Do not look up unqualified name
as a TLD. */
#define RES_NORELOAD 0x02000000 /* No automatic configuration reload. */
+#define RES_TRUSTAD 0x04000000 /* Request AD bit, keep it in responses. */
#define RES_DEFAULT (RES_RECURSE|RES_DEFNAMES|RES_DNSRCH)
@@ -127,6 +127,7 @@
"single-request-reopen");
print_option_flag (fp, &options, RES_NOTLDQUERY, "no-tld-query");
print_option_flag (fp, &options, RES_NORELOAD, "no-reload");
+ print_option_flag (fp, &options, RES_TRUSTAD, "trust-ad");
fputc ('\n', fp);
if (options != 0)
fprintf (fp, "; error: unresolved option bits: 0x%x\n", options);
@@ -711,6 +712,15 @@
"nameserver 192.0.2.1\n"
"; nameserver[0]: [192.0.2.1]:53\n"
},
+ {.name = "trust-ad flag",
+ .conf = "options trust-ad\n"
+ "nameserver 192.0.2.1\n",
+ .expected = "options trust-ad\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 }
};
new file mode 100644
@@ -0,0 +1,200 @@
+/* Test the behavior of the trust-ad option.
+ Copyright (C) 2019 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 <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/check_nss.h>
+#include <support/resolv_test.h>
+#include <support/support.h>
+
+/* This controls properties of the response. volatile because
+ __res_send is incorrectly declared as __THROW. */
+static volatile unsigned char response_number;
+static volatile bool response_ad_bit;
+static volatile bool query_ad_bit;
+
+static void
+response (const struct resolv_response_context *ctx,
+ struct resolv_response_builder *b,
+ const char *qname, uint16_t qclass, uint16_t qtype)
+{
+ TEST_COMPARE (qclass, C_IN);
+ TEST_COMPARE (qtype, T_A);
+ TEST_COMPARE_STRING (qname, "www.example");
+
+ HEADER header;
+ memcpy (&header, ctx->query_buffer, sizeof (header));
+ TEST_COMPARE (header.ad, query_ad_bit);
+
+ struct resolv_response_flags flags = { .ad = response_ad_bit, };
+ resolv_response_init (b, flags);
+ resolv_response_add_question (b, qname, qclass, qtype);
+ resolv_response_section (b, ns_s_an);
+ resolv_response_open_record (b, qname, qclass, T_A, 0x12345678);
+ char addr[4] = { 192, 0, 2, response_number };
+ resolv_response_add_data (b, addr, sizeof (addr));
+ resolv_response_close_record (b);
+}
+
+static void
+check_answer (const unsigned char *buffer, size_t buffer_length,
+ bool expected_ad)
+{
+ HEADER header;
+ TEST_VERIFY (buffer_length > sizeof (header));
+ memcpy (&header, buffer, sizeof (header));
+ TEST_COMPARE (0, header.aa);
+ TEST_COMPARE (expected_ad, header.ad);
+ TEST_COMPARE (0, header.opcode);
+ TEST_COMPARE (1, header.qr);
+ TEST_COMPARE (0, header.rcode);
+ TEST_COMPARE (1, header.rd);
+ TEST_COMPARE (0, header.tc);
+ TEST_COMPARE (1, ntohs (header.qdcount));
+ TEST_COMPARE (1, ntohs (header.ancount));
+ TEST_COMPARE (0, ntohs (header.nscount));
+ TEST_COMPARE (0, ntohs (header.arcount));
+
+ char *description = xasprintf ("response=%d ad=%d",
+ response_number, expected_ad);
+ char *expected = xasprintf ("name: www.example\n"
+ "address: 192.0.2.%d\n", response_number);
+ check_dns_packet (description, buffer, buffer_length, expected);
+ free (expected);
+ free (description);
+}
+
+static int
+do_test (void)
+{
+ struct resolv_test *aux = resolv_test_start
+ ((struct resolv_redirect_config)
+ {
+ .response_callback = response,
+ });
+
+ /* By default, the resolver is not trusted, and the AD bit is
+ cleared. */
+
+ static const unsigned char hand_crafted_query[] =
+ {
+ 10, 11, /* Transaction ID. */
+ 1, 0x20, /* Query with RD, AD flags. */
+ 0, 1, /* One question. */
+ 0, 0, 0, 0, 0, 0, /* The other sections are empty. */
+ 3, 'w', 'w', 'w', 7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 0,
+ 0, T_A, /* A query. */
+ 0, 1, /* Class IN. */
+ };
+
+ ++response_number;
+ response_ad_bit = false;
+
+ unsigned char buffer[512];
+ memset (buffer, 255, sizeof (buffer));
+ query_ad_bit = true;
+ int ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+ buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+
+ ++response_number;
+ memset (buffer, 255, sizeof (buffer));
+ query_ad_bit = false;
+ ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+ response_ad_bit = true;
+
+ response_ad_bit = true;
+
+ ++response_number;
+ query_ad_bit = true;
+ ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+ buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+
+ ++response_number;
+ memset (buffer, 255, sizeof (buffer));
+ query_ad_bit = false;
+ ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+
+ /* No AD bit set in generated queries. */
+ memset (buffer, 255, sizeof (buffer));
+ ret = res_mkquery (QUERY, "www.example", C_IN, T_A,
+ (const unsigned char *) "", 0, NULL,
+ buffer, sizeof (buffer));
+ HEADER header;
+ memcpy (&header, buffer, sizeof (header));
+ TEST_VERIFY (!header.ad);
+
+ /* With RES_TRUSTAD, the AD bit is passed through if it set in the
+ response. It is also included in queries. */
+
+ _res.options |= RES_TRUSTAD;
+ query_ad_bit = true;
+
+ response_ad_bit = false;
+
+ ++response_number;
+ memset (buffer, 255, sizeof (buffer));
+ ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+ buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+
+ ++response_number;
+ memset (buffer, 255, sizeof (buffer));
+ ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, false);
+
+ response_ad_bit = true;
+
+ ++response_number;
+ memset (buffer, 0, sizeof (buffer));
+ ret = res_send (hand_crafted_query, sizeof (hand_crafted_query),
+ buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, true);
+
+ ++response_number;
+ memset (buffer, 0, sizeof (buffer));
+ ret = res_query ("www.example", C_IN, T_A, buffer, sizeof (buffer));
+ TEST_VERIFY (ret > 0);
+ check_answer (buffer, ret, true);
+
+ /* AD bit set in generated queries. */
+ memset (buffer, 0, sizeof (buffer));
+ ret = res_mkquery (QUERY, "www.example", C_IN, T_A,
+ (const unsigned char *) "", 0, NULL,
+ buffer, sizeof (buffer));
+ memcpy (&header, buffer, sizeof (header));
+ TEST_VERIFY (header.ad);
+
+ resolv_test_end (aux);
+
+ return 0;
+}
+
+#include <support/test-driver.c>
@@ -182,6 +182,8 @@
if (flags.tc)
b->buffer[2] |= 0x02;
b->buffer[3] = 0x80 | flags.rcode; /* Always set RA. */
+ if (flags.ad)
+ b->buffer[3] |= 0x20;
/* Fill in the initial section count values. */
b->buffer[4] = flags.qdcount >> 8;
@@ -134,6 +134,9 @@
/* If true, the TC (truncation) flag will be set. */
bool tc;
+ /* If true, the AD (authenticated data) flag will be set. */
+ bool ad;
+
/* Initial section count values. Can be used to artificially
increase the counts, for malformed packet testing.*/
unsigned short qdcount;