diff --git a/bin/tests/system/dnstap/ns1/named.conf.in b/bin/tests/system/dnstap/ns1/named.conf.in index daaf88731a..4d06bb7486 100644 --- a/bin/tests/system/dnstap/ns1/named.conf.in +++ b/bin/tests/system/dnstap/ns1/named.conf.in @@ -30,6 +30,7 @@ options { require-server-cookie no; dnssec-validation yes; qname-minimization disabled; + grease-edns-negotiation no; }; trust-anchors { }; diff --git a/bin/tests/system/dnstap/ns2/named.conf.in b/bin/tests/system/dnstap/ns2/named.conf.in index 12ae86322d..07a1b27fa1 100644 --- a/bin/tests/system/dnstap/ns2/named.conf.in +++ b/bin/tests/system/dnstap/ns2/named.conf.in @@ -30,6 +30,7 @@ options { require-server-cookie no; dnssec-validation yes; qname-minimization disabled; + grease-edns-negotiation no; }; trust-anchors { }; diff --git a/bin/tests/system/dnstap/ns3/named.conf.in b/bin/tests/system/dnstap/ns3/named.conf.in index d443c3818a..f85fc2a7cb 100644 --- a/bin/tests/system/dnstap/ns3/named.conf.in +++ b/bin/tests/system/dnstap/ns3/named.conf.in @@ -31,6 +31,7 @@ options { minimal-responses no; dnssec-validation yes; qname-minimization disabled; + grease-edns-negotiation no; }; trust-anchors { }; diff --git a/bin/tests/system/dnstap/ns4/named.conf.in b/bin/tests/system/dnstap/ns4/named.conf.in index 18c4da1100..1932fa1d2a 100644 --- a/bin/tests/system/dnstap/ns4/named.conf.in +++ b/bin/tests/system/dnstap/ns4/named.conf.in @@ -30,6 +30,7 @@ options { require-server-cookie no; dnssec-validation yes; qname-minimization disabled; + grease-edns-negotiation no; }; trust-anchors { }; diff --git a/bin/tests/system/forward/ns7/named.conf.in b/bin/tests/system/forward/ns7/named.conf.in index c6e55a89bc..fbfd943e48 100644 --- a/bin/tests/system/forward/ns7/named.conf.in +++ b/bin/tests/system/forward/ns7/named.conf.in @@ -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 "." { diff --git a/bin/tests/system/resolver/ns5/named.conf.in b/bin/tests/system/resolver/ns5/named.conf.in index 32c5fa7111..9b5ef5f833 100644 --- a/bin/tests/system/resolver/ns5/named.conf.in +++ b/bin/tests/system/resolver/ns5/named.conf.in @@ -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"; diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 7693c08252..1b6eaa395a 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -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;