Send and check grease queries

Grease queries exercise otherwise unexercised parts of the DNS
protocol.
This commit is contained in:
Mark Andrews
2025-03-06 14:22:36 +11:00
parent c0b3de16a7
commit 7289d291bf
7 changed files with 277 additions and 36 deletions
@@ -30,6 +30,7 @@ options {
require-server-cookie no;
dnssec-validation yes;
qname-minimization disabled;
grease-edns-negotiation no;
};
trust-anchors { };
@@ -30,6 +30,7 @@ options {
require-server-cookie no;
dnssec-validation yes;
qname-minimization disabled;
grease-edns-negotiation no;
};
trust-anchors { };
@@ -31,6 +31,7 @@ options {
minimal-responses no;
dnssec-validation yes;
qname-minimization disabled;
grease-edns-negotiation no;
};
trust-anchors { };
@@ -30,6 +30,7 @@ options {
require-server-cookie no;
dnssec-validation yes;
qname-minimization disabled;
grease-edns-negotiation no;
};
trust-anchors { };
@@ -24,6 +24,11 @@ options {
dnssec-validation yes;
};
server 10.53.0.1 {
/* Force EDNS(0) so priming packet counts are stable */
edns-version 0;
};
trust-anchors { };
zone "." {
@@ -34,6 +34,10 @@ server 10.53.0.7 {
edns-version 0;
};
server 10.53.0.10 {
request-nsid no;
};
zone "." {
type hint;
file "root.hint";
+264 -36
View File
@@ -24,6 +24,7 @@
#include <isc/counter.h>
#include <isc/hash.h>
#include <isc/hashmap.h>
#include <isc/hex.h>
#include <isc/log.h>
#include <isc/loop.h>
#include <isc/mutex.h>
@@ -269,6 +270,9 @@ typedef struct query {
unsigned int options;
unsigned int attributes;
unsigned int udpsize;
unsigned char *grease_nsid;
uint16_t grease_ednsflags;
uint16_t grease_nsid_len;
unsigned char data[512];
} resquery_t;
@@ -293,7 +297,10 @@ struct tried {
#define QUERY_MAGIC ISC_MAGIC('Q', '!', '!', '!')
#define VALID_QUERY(query) ISC_MAGIC_VALID(query, QUERY_MAGIC)
#define RESQUERY_ATTR_CANCELED 0x02
#define RESQUERY_ATTR_CANCELED (1U << 0)
#define RESQUERY_ATTR_DNS_FLAGS (1U << 1)
#define RESQUERY_ATTR_EDNS_FLAGS (1U << 2)
#define RESQUERY_ATTR_EDNS_NEG (1U << 3)
#define RESQUERY_CONNECTING(q) ((q)->connects > 0)
#define RESQUERY_CANCELED(q) (((q)->attributes & RESQUERY_ATTR_CANCELED) != 0)
@@ -1811,12 +1818,13 @@ detach:
static isc_result_t
fctx_addopt(dns_message_t *message, unsigned int version, uint16_t udpsize,
dns_ednsopt_t *ednsopts, size_t count) {
unsigned int flags, dns_ednsopt_t *ednsopts, size_t count) {
dns_rdataset_t *rdataset = NULL;
isc_result_t result;
result = dns_message_buildopt(message, &rdataset, version, udpsize,
DNS_MESSAGEEXTFLAG_DO, ednsopts, count);
flags | DNS_MESSAGEEXTFLAG_DO, ednsopts,
count);
if (result != ISC_R_SUCCESS) {
return result;
}
@@ -2361,6 +2369,16 @@ resquery_send(resquery_t *query) {
isc_region_t zr;
isc_buffer_t zb;
#endif /* HAVE_DNSTAP */
bool grease_dns_flags = res->view->grease_dns_flags;
bool grease_edns_flags = res->view->grease_edns_flags;
bool grease_edns_neg = res->view->grease_edns_neg;
bool grease_nsid = false;
bool reqnsid = res->view->requestnsid;
unsigned int grease_rate = res->view->grease_rate;
time_t grease_until = res->view->grease_until;
#define GREASE \
(grease_rate > 0 ? (grease_rate >= isc_random_uniform(99) + 1) : false)
time_t now = time(NULL);
QTRACE("send");
@@ -2444,6 +2462,22 @@ resquery_send(resquery_t *query) {
isc_netaddr_fromsockaddr(&ipaddr, &query->addrinfo->sockaddr);
(void)dns_peerlist_peerbyaddr(fctx->res->view->peers, &ipaddr, &peer);
if (peer != NULL) {
dns_peer_getdnsflags(peer, &grease_dns_flags);
dns_peer_getednsflags(peer, &grease_edns_flags);
dns_peer_getednsneg(peer, &grease_edns_neg);
}
/*
* GREASE: Set the final reserved DNS header bit.
*/
query->attributes &= ~RESQUERY_ATTR_DNS_FLAGS;
if (grease_dns_flags && now < grease_until && GREASE) {
fctx->qmessage->flags |= 0x40;
query->attributes |= RESQUERY_ATTR_DNS_FLAGS;
grease_nsid = res->view->grease_nsid;
}
/*
* The ADB does not know about servers with "edns no". Check
* this, and then inform the ADB for future use.
@@ -2506,11 +2540,46 @@ resquery_send(resquery_t *query) {
uint16_t peerudpsize = 0;
unsigned int version = DNS_EDNS_VERSION;
unsigned int flags = query->addrinfo->flags;
bool reqnsid = res->view->requestnsid;
bool sendcookie = res->view->sendcookie;
bool tcpkeepalive = false;
unsigned char cookie[COOKIE_BUFFER_SIZE];
uint16_t padding = 0;
uint16_t ednsflags = 0;
/*
* GREASE: EDNS version negotiation by sending a
* large EDNS version. We should get back BADVERS
* if there is an OPT record present with the version
* set to 0 as EDNS(1) is currently undefined.
*/
query->attributes &= ~RESQUERY_ATTR_EDNS_NEG;
if (grease_edns_neg && GREASE) {
version = 100;
query->attributes |= RESQUERY_ATTR_EDNS_NEG;
grease_nsid = res->view->grease_nsid;
}
/*
* GREASE: Unknown EDNS flags must be zero on
* transmission and be ignored on reception.
* Choose a random unallocated flag bit.
* Disable test Jan 1, 2026.
*/
query->attributes &= ~RESQUERY_ATTR_EDNS_FLAGS;
if (grease_edns_flags && now < grease_until && GREASE) {
/*
* Set one of the reserved EDNS flag bits.
*/
ednsflags = 1 << isc_random_uniform(14);
ednsflags &=
~res->view->grease_edns_known_flags;
if (ednsflags != 0) {
query->attributes |=
RESQUERY_ATTR_EDNS_FLAGS;
query->grease_ednsflags = ednsflags;
grease_nsid = res->view->grease_nsid;
}
}
/*
* Set the default UDP size to what was
@@ -2544,6 +2613,7 @@ resquery_send(resquery_t *query) {
if ((flags & DNS_FETCHOPT_EDNSVERSIONSET) != 0) {
version = flags & DNS_FETCHOPT_EDNSVERSIONMASK;
version >>= DNS_FETCHOPT_EDNSVERSIONSHIFT;
query->attributes &= ~RESQUERY_ATTR_EDNS_NEG;
}
/* Request NSID/COOKIE/VERSION for current peer?
@@ -2551,6 +2621,8 @@ resquery_send(resquery_t *query) {
if (peer != NULL) {
uint8_t ednsversion;
(void)dns_peer_getrequestnsid(peer, &reqnsid);
(void)dns_peer_getrequestnsid(peer,
&grease_nsid);
(void)dns_peer_getsendcookie(peer, &sendcookie);
result = dns_peer_getednsversion(peer,
&ednsversion);
@@ -2558,12 +2630,14 @@ resquery_send(resquery_t *query) {
ednsversion < version)
{
version = ednsversion;
query->attributes &=
~RESQUERY_ATTR_EDNS_NEG;
}
}
if (NOCOOKIE(query->addrinfo)) {
sendcookie = false;
}
if (reqnsid) {
if (reqnsid || grease_nsid) {
INSIST(ednsopt < DNS_EDNSOPTIONS);
ednsopts[ednsopt].code = DNS_OPT_NSID;
ednsopts[ednsopt].length = 0;
@@ -2623,7 +2697,7 @@ resquery_send(resquery_t *query) {
query->ednsversion = version;
result = fctx_addopt(fctx->qmessage, version, udpsize,
ednsopts, ednsopt);
ednsflags, ednsopts, ednsopt);
if (reqnsid && result == ISC_R_SUCCESS) {
query->options |= DNS_FETCHOPT_WANTNSID;
} else if (result != ISC_R_SUCCESS) {
@@ -4912,6 +4986,9 @@ log_lame(fetchctx_t *fctx, dns_adbaddrinfo_t *addrinfo) {
namebuf, domainbuf, addrbuf);
}
static void
log_formerr(fetchctx_t *fctx, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
static void
log_formerr(fetchctx_t *fctx, const char *format, ...) {
char nsbuf[ISC_SOCKADDR_FORMATSIZE];
@@ -4930,6 +5007,27 @@ log_formerr(fetchctx_t *fctx, const char *format, ...) {
fctx->info, fctx->clientstr, msgbuf);
}
static void
log_grease(fetchctx_t *fctx, const char *test, const char *format, ...)
ISC_FORMAT_PRINTF(3, 4);
static void
log_grease(fetchctx_t *fctx, const char *test, const char *format, ...) {
char nsbuf[ISC_SOCKADDR_FORMATSIZE];
char msgbuf[2048];
va_list args;
va_start(args, format);
vsnprintf(msgbuf, sizeof(msgbuf), format, args);
va_end(args);
isc_sockaddr_format(&fctx->addrinfo->sockaddr, nsbuf, sizeof(nsbuf));
isc_log_write(DNS_LOGCATEGORY_GREASE, DNS_LOGMODULE_RESOLVER,
ISC_LOG_NOTICE, "%s: %s %s: %s", test, nsbuf, fctx->info,
msgbuf);
}
static isc_result_t
same_question(fetchctx_t *fctx, dns_message_t *message) {
isc_result_t result;
@@ -7270,17 +7368,38 @@ checknames(dns_message_t *message) {
checknamessection(message, DNS_SECTION_ADDITIONAL);
}
static void
make_hex(unsigned char *src, size_t srclen, char *buf, size_t buflen) {
isc_buffer_t b;
isc_region_t r;
isc_result_t result;
r.base = src;
r.length = srclen;
isc_buffer_init(&b, buf, buflen);
result = isc_hex_totext(&r, 0, "", &b);
RUNTIME_CHECK(result == ISC_R_SUCCESS);
isc_buffer_putuint8(&b, '\0');
}
static void
make_printable(unsigned char *src, size_t srclen, char *buf, size_t buflen) {
INSIST(buflen > srclen);
while (srclen-- > 0) {
unsigned char c = *src++;
*buf++ = isprint(c) ? c : '.';
}
*buf = '\0';
}
/*
* Log server NSID at log level 'level'
*/
static void
log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
isc_mem_t *mctx) {
static const char hex[17] = "0123456789abcdef";
char addrbuf[ISC_SOCKADDR_FORMATSIZE];
char addrbuf[ISC_SOCKADDR_FORMATSIZE], *buf = NULL, *pbuf = NULL;
size_t buflen;
unsigned char *p, *nsid;
unsigned char *buf = NULL, *pbuf = NULL;
REQUIRE(nsid_len <= UINT16_MAX);
@@ -7290,20 +7409,10 @@ log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level,
pbuf = isc_mem_get(mctx, nsid_len + 1);
/* Convert to hex */
p = buf;
nsid = isc_buffer_current(opt);
for (size_t i = 0; i < nsid_len; i++) {
*p++ = hex[(nsid[i] >> 4) & 0xf];
*p++ = hex[nsid[i] & 0xf];
}
*p = '\0';
make_hex(isc_buffer_current(opt), nsid_len, buf, buflen);
/* Make printable version */
p = pbuf;
for (size_t i = 0; i < nsid_len; i++) {
*p++ = isprint(nsid[i]) ? nsid[i] : '.';
}
*p = '\0';
make_printable(isc_buffer_current(opt), nsid_len, pbuf, nsid_len + 1);
isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf,
sizeof(addrbuf));
@@ -8108,6 +8217,10 @@ rctx_opt(respctx_t *rctx) {
}
seen_nsid = true;
/* Save pointer to the NSID for log_grease. */
query->grease_nsid = isc_buffer_current(&optbuf);
query->grease_nsid_len = optlen;
if ((query->options & DNS_FETCHOPT_WANTNSID) != 0) {
log_nsid(&optbuf, optlen, query, ISC_LOG_INFO,
fctx->mctx);
@@ -8160,8 +8273,16 @@ rctx_opt(respctx_t *rctx) {
*/
static void
rctx_edns(respctx_t *rctx) {
resquery_t *query = rctx->query;
fetchctx_t *fctx = rctx->fctx;
resquery_t *query = rctx->query;
char *nsid_buf = NULL;
char *nsid_pbuf = NULL;
const char *grease_sep_1 = "";
const char *grease_sep_2 = "";
const char *grease_sep_3 = "";
const char *nsid_hex = "";
const char *nsid_print = "";
size_t nsid_buflen = 0;
/*
* We have an affirmative response to the query and we have
@@ -8211,6 +8332,120 @@ rctx_edns(respctx_t *rctx) {
FCTX_ADDRINFO_NOEDNS0);
}
#define GREASE_NSID_FMT "%s%s%s%s%s"
#define GREASE_NSID_INFO \
grease_sep_1, nsid_hex, grease_sep_2, nsid_print, grease_sep_3
/*
* Turn NSID into printable forms.
*/
if (query->grease_nsid != NULL && query->grease_nsid_len != 0) {
nsid_buflen = query->grease_nsid_len * 2 + 1;
nsid_hex = nsid_buf = isc_mem_get(fctx->mctx, nsid_buflen);
nsid_print = nsid_pbuf =
isc_mem_get(fctx->mctx, query->grease_nsid_len + 1);
/* Convert to hex */
make_hex(query->grease_nsid, query->grease_nsid_len, nsid_buf,
nsid_buflen);
/* Convert to printable */
make_printable(query->grease_nsid, query->grease_nsid_len,
nsid_pbuf, query->grease_nsid_len + 1);
grease_sep_1 = " (NSID ";
grease_sep_2 = " (";
grease_sep_3 = "))";
}
/*
* GREASE: Check that the final DNS header flag is not
* echoed back.
*/
if ((query->attributes & RESQUERY_ATTR_DNS_FLAGS) != 0) {
/*
* The last DNS header flag should be ignored
* by the received and be zero when transmitted.
*/
if ((query->rmessage->flags & 0x40) != 0) {
log_grease(
fctx, "grease-dns-flags",
"DNS header flag 0x40 not zero" GREASE_NSID_FMT,
GREASE_NSID_INFO);
}
}
/*
* GREASE: Check that the unknown EDNS flags bit are not
* echoed back.
*/
if ((query->attributes & RESQUERY_ATTR_EDNS_FLAGS) != 0 &&
rctx->opt != NULL)
{
uint16_t ednsflags = rctx->opt->ttl & 0xffff;
/*
* Ignore known flags.
*/
ednsflags &= ~fctx->res->view->grease_edns_known_flags;
/*
* Was our unspecified EDNS flag echoed back?
*/
if ((ednsflags & query->grease_ednsflags) != 0) {
log_grease(fctx, "grease-edns-flags",
"Unspecified EDNS flags not zero: "
"%04x" GREASE_NSID_FMT,
ednsflags, GREASE_NSID_INFO);
}
}
/*
* GREASE: Check that we have the expected rcode (BADVERS)
* and version(s) (0).
*/
if ((query->attributes & RESQUERY_ATTR_EDNS_NEG) != 0 &&
rctx->opt != NULL)
{
uint8_t version = (rctx->opt->ttl >> 16) & 0xff;
/*
* We expect the rcode to be BADVERS because we sent EDNS
* version 100.
*/
if (query->rmessage->rcode != dns_rcode_badvers) {
log_grease(fctx, "grease-edns-negotiation",
"EDNS version negotiation: rcode != "
"BADVERS(16): %u" GREASE_NSID_FMT,
query->rmessage->rcode, GREASE_NSID_INFO);
}
/*
* Only EDNS(0) is currently specified.
*/
if (version > fctx->res->view->grease_edns_max_version) {
log_grease(fctx, "grease-edns-negotiation",
"EDNS version negotiation: version > %u: "
"%u" GREASE_NSID_FMT,
fctx->res->view->grease_edns_max_version,
version, GREASE_NSID_INFO);
}
}
/*
* Cleanup NSID buffers.
*/
if (nsid_pbuf != NULL) {
isc_mem_put(fctx->mctx, nsid_pbuf, query->grease_nsid_len + 1);
}
if (nsid_buf != NULL) {
isc_mem_put(fctx->mctx, nsid_buf, nsid_buflen);
}
#undef GREASE_NSID_FMT
#undef GREASE_NSID_INFO
/*
* If we get a non error EDNS response record the fact so we
* won't fallback to plain DNS in the future for this server.
@@ -9844,18 +10079,14 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
}
} else if (rcode == dns_rcode_badvers) {
unsigned int version;
#if DNS_EDNS_VERSION > 0
unsigned int flags, mask;
#endif /* if DNS_EDNS_VERSION > 0 */
INSIST(rctx->opt != NULL);
version = (rctx->opt->ttl >> 16) & 0xff;
#if DNS_EDNS_VERSION > 0
flags = (version << DNS_FETCHOPT_EDNSVERSIONSHIFT) |
DNS_FETCHOPT_EDNSVERSIONSET;
mask = DNS_FETCHOPT_EDNSVERSIONMASK |
DNS_FETCHOPT_EDNSVERSIONSET;
#endif /* if DNS_EDNS_VERSION > 0 */
/*
* Record that we got a good EDNS response.
@@ -9876,7 +10107,6 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
* version checking of badvers responses. We won't
* be sending COOKIE etc. in that case.
*/
#if DNS_EDNS_VERSION > 0
if ((int)version < query->ednsversion) {
dns_adb_changeflags(fctx->adb, query->addrinfo, flags,
mask);
@@ -9885,10 +10115,6 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
rctx->broken_server = DNS_R_BADVERS;
rctx->next_server = true;
}
#else /* if DNS_EDNS_VERSION > 0 */
rctx->broken_server = DNS_R_BADVERS;
rctx->next_server = true;
#endif /* if DNS_EDNS_VERSION > 0 */
} else if (rcode == dns_rcode_badcookie && rctx->query->rmessage->cc_ok)
{
/*
@@ -9904,10 +10130,12 @@ rctx_badserver(respctx_t *rctx, isc_result_t result) {
rctx->next_server = true;
}
isc_buffer_init(&b, code, sizeof(code) - 1);
dns_rcode_totext(rcode, &b);
code[isc_buffer_usedlength(&b)] = '\0';
FCTXTRACE2("remote server broken: returned ", code);
if (rctx->broken_server != ISC_R_SUCCESS) {
isc_buffer_init(&b, code, sizeof(code) - 1);
dns_rcode_totext(rcode, &b);
code[isc_buffer_usedlength(&b)] = '\0';
FCTXTRACE2("remote server broken: returned ", code);
}
rctx_done(rctx, result);
return ISC_R_COMPLETE;