diff --git a/bin/dig/dig.c b/bin/dig/dig.c index ff3148eacf..04aafc4452 100644 --- a/bin/dig/dig.c +++ b/bin/dig/dig.c @@ -328,6 +328,7 @@ help(void) { " +[no]yaml (Present the results as " "YAML)\n" " +[no]zflag (Set Z flag in query)\n" + " +[no]zoneversion (Request zone version)\n" " global d-opts and servers (before host name) affect " "all " "queries.\n" @@ -2574,9 +2575,22 @@ plus_option(char *option, bool is_batchfile, bool *need_clone, lookup->rrcomments = -1; } break; - case 'z': /* zflag */ - FULLCHECK("zflag"); - lookup->zflag = state; + case 'z': + switch (cmd[1]) { + case 'f': /* zflag */ + FULLCHECK("zflag"); + lookup->zflag = state; + break; + case 'o': /* zoneversion */ + FULLCHECK("zoneversion"); + if (state && lookup->edns == -1) { + lookup->edns = DEFAULT_EDNS_VERSION; + } + lookup->zoneversion = state; + break; + default: + goto invalid_option; + } break; default: invalid_option: diff --git a/bin/dig/dig.rst b/bin/dig/dig.rst index 3b961f8fee..dfb6893a68 100644 --- a/bin/dig/dig.rst +++ b/bin/dig/dig.rst @@ -757,6 +757,10 @@ abbreviation is unambiguous; for example, :option:`+cd` is equivalent to This option sets [or does not set] the last unassigned DNS header flag in a DNS query. This flag is off by default. +.. option:: +zoneversion, +nozoneversion + + When enabled, this option includes an EDNS Zone Version request when sending a query. + Multiple Queries ~~~~~~~~~~~~~~~~ diff --git a/bin/dig/dighost.c b/bin/dig/dighost.c index 5069af6b4f..954fbdd2e7 100644 --- a/bin/dig/dighost.c +++ b/bin/dig/dighost.c @@ -705,6 +705,7 @@ clone_lookup(dig_lookup_t *lookold, bool servers) { looknew->opcode = lookold->opcode; looknew->expire = lookold->expire; looknew->nsid = lookold->nsid; + looknew->zoneversion = lookold->zoneversion; looknew->tcp_keepalive = lookold->tcp_keepalive; looknew->header_only = lookold->header_only; looknew->https_mode = lookold->https_mode; @@ -2591,6 +2592,14 @@ setup_lookup(dig_lookup_t *lookup) { i++; } + if (lookup->zoneversion) { + INSIST(i < MAXOPTS); + opts[i].code = DNS_OPT_ZONEVERSION; + opts[i].length = 0; + opts[i].value = NULL; + i++; + } + if (lookup->ednsoptscnt != 0) { INSIST(i + lookup->ednsoptscnt <= MAXOPTS); memmove(&opts[i], lookup->ednsopts, diff --git a/bin/dig/dighost.h b/bin/dig/dighost.h index db8c842f75..f1670280b4 100644 --- a/bin/dig/dighost.h +++ b/bin/dig/dighost.h @@ -121,7 +121,7 @@ struct dig_lookup { tcp_mode, tcp_mode_set, tls_mode, /*% connect using TLS */ trace, /*% dig +trace */ trace_root, /*% initial query for either +trace or +nssearch */ - ttlunits, use_usec, waiting_connect, zflag; + ttlunits, use_usec, waiting_connect, zflag, zoneversion; char textname[MXNAME]; /*% Name we're going to be looking up */ char cmdline[MXNAME]; dns_rdatatype_t rdtype; diff --git a/bin/named/config.c b/bin/named/config.c index 3ac41a80b2..c3bc87b0ab 100644 --- a/bin/named/config.c +++ b/bin/named/config.c @@ -99,6 +99,7 @@ options {\n\ recursing-file \"named.recursing\";\n\ recursive-clients 1000;\n\ request-nsid false;\n\ + request-zoneversion false;\n\ resolver-query-timeout 10;\n\ # responselog ;\n\ rrset-order { order random; };\n\ @@ -239,6 +240,7 @@ options {\n\ notify yes;\n\ notify-delay 5;\n\ notify-to-soa no;\n\ + provide-zoneversion yes;\n\ send-report-channel .;\n\ serial-update-method increment;\n\ sig-signing-nodes 100;\n\ @@ -260,6 +262,7 @@ view \"_bind\" chaos {\n\ notify no;\n\ allow-new-zones no;\n\ max-cache-size 2M;\n\ + provide-zoneversion no;\n\ \n\ # Prevent use of this zone in DNS amplified reflection DoS attacks\n\ rate-limit {\n\ diff --git a/bin/named/server.c b/bin/named/server.c index bffe2333f3..f57a727654 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -1391,6 +1391,13 @@ configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) { CHECK(dns_peer_setrequestnsid(peer, cfg_obj_asboolean(obj))); } + obj = NULL; + (void)cfg_map_get(cpeer, "request-zoneversion", &obj); + if (obj != NULL) { + CHECK(dns_peer_setrequestzoneversion(peer, + cfg_obj_asboolean(obj))); + } + obj = NULL; (void)cfg_map_get(cpeer, "send-cookie", &obj); if (obj != NULL) { @@ -3246,6 +3253,7 @@ create_empty_zone(dns_zone_t *pzone, dns_name_t *name, dns_view_t *view, dns_zone_setoption(zone, ~DNS_ZONEOPT_NOCHECKNS, false); dns_zone_setoption(zone, DNS_ZONEOPT_NOCHECKNS, true); + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION, false); dns_zone_setcheckdstype(zone, dns_checkdstype_no); dns_zone_setnotifytype(zone, dns_notifytype_no); dns_zone_setautomatic(zone, true); @@ -5132,6 +5140,11 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, INSIST(result == ISC_R_SUCCESS); view->requestnsid = cfg_obj_asboolean(obj); + obj = NULL; + result = named_config_get(maps, "request-zoneversion", &obj); + INSIST(result == ISC_R_SUCCESS); + view->requestzoneversion = cfg_obj_asboolean(obj); + obj = NULL; result = named_config_get(maps, "send-cookie", &obj); INSIST(result == ISC_R_SUCCESS); diff --git a/bin/named/statschannel.c b/bin/named/statschannel.c index cc9c29d3fd..c965ac77ee 100644 --- a/bin/named/statschannel.c +++ b/bin/named/statschannel.c @@ -358,6 +358,8 @@ init_desc(void) { SET_NSSTATDESC(expireopt, "Expire option received", "ExpireOpt"); SET_NSSTATDESC(keepaliveopt, "EDNS TCP keepalive option received", "KeepAliveOpt"); + SET_NSSTATDESC(zoneversionopt, "ZONEVERSION option received", + "ZoneVersionOpt"); SET_NSSTATDESC(padopt, "EDNS padding option received", "PadOpt"); SET_NSSTATDESC(otheropt, "Other EDNS option received", "OtherOpt"); SET_NSSTATDESC(cookiein, "COOKIE option received", "CookieIn"); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 35f5301344..cdcf115e70 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -1227,6 +1227,12 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, dns_zone_setkasp(zone, NULL); } + obj = NULL; + result = named_config_get(maps, "provide-zoneversion", &obj); + INSIST(result == ISC_R_SUCCESS && obj != NULL); + dns_zone_setoption(zone, DNS_ZONEOPT_ZONEVERSION, + cfg_obj_asboolean(obj)); + obj = NULL; result = named_config_get(maps, "notify", &obj); INSIST(result == ISC_R_SUCCESS && obj != NULL); diff --git a/bin/tests/system/checkconf/good-server-christmas-tree.conf.in b/bin/tests/system/checkconf/good-server-christmas-tree.conf.in index 2eceee6baf..6353e191cd 100644 --- a/bin/tests/system/checkconf/good-server-christmas-tree.conf.in +++ b/bin/tests/system/checkconf/good-server-christmas-tree.conf.in @@ -31,6 +31,7 @@ server 0.0.0.0 { request-ixfr no; request-ixfr-max-diffs 0; request-nsid no; + request-zoneversion no; require-cookie no; send-cookie no; tcp-keepalive no; @@ -55,6 +56,7 @@ server :: { request-ixfr no; request-ixfr-max-diffs 0; request-nsid no; + request-zoneversion no; require-cookie no; send-cookie no; tcp-keepalive no; diff --git a/bin/tests/system/digdelv/ns2/named.conf.in b/bin/tests/system/digdelv/ns2/named.conf.in index c9fdcd79ab..47b0449ffb 100644 --- a/bin/tests/system/digdelv/ns2/named.conf.in +++ b/bin/tests/system/digdelv/ns2/named.conf.in @@ -36,4 +36,5 @@ zone "example" { zone "example.tld" { type primary; file "example.tld.db"; + provide-zoneversion no; }; diff --git a/bin/tests/system/digdelv/tests.sh b/bin/tests/system/digdelv/tests.sh index 9c7f42d730..60f5e1f022 100644 --- a/bin/tests/system/digdelv/tests.sh +++ b/bin/tests/system/digdelv/tests.sh @@ -801,6 +801,73 @@ if [ -x "$DIG" ]; then if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) + n=$((n + 1)) + echo_i "checking dig +zoneversion to a authoritative server ($n)" + ret=0 + dig_with_opts @10.53.0.2 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1 + pat="; ZONEVERSION: ZONE: example, SOA-SERIAL: 2000042407" + grep "$pat" dig.out.test$n >/dev/null || ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + n=$((n + 1)) + echo_i "checking dig +zoneversion to a authoritative server with zoneversion disabled ($n)" + ret=0 + dig_with_opts @10.53.0.2 +zoneversion a.example.tld >dig.out.test$n 2>&1 || ret=1 + grep "status: NOERROR" dig.out.test$n >/dev/null || ret=1 + grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + if [ $HAS_PYYAML -ne 0 ]; then + n=$((n + 1)) + echo_i "checking dig +yaml +zoneversion to a authoritative server ($n)" + ret=0 + dig_with_opts @10.53.0.2 +yaml +zoneversion a.example >dig.out.test$n 2>&1 || ret=1 + $PYTHON yamlget.py dig.out.test$n 0 message response_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1 + read -r value to a authoritative server ($n)" + ret=0 + dig_with_opts @10.53.0.2 +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1 + grep "status: FORMERR" dig.out.test$n >/dev/null || ret=1 + grep "; ZONEVERSION:" dig.out.test$n >/dev/null && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + n=$((n + 1)) + echo_i "checking dig +zoneversion to a recursive server ($n)" + ret=0 + dig_with_opts @10.53.0.3 +zoneversion a.example >dig.out.test$n 2>&1 || ret=1 + grep '; ZONEVERSION:' dig.out.test$n >/dev/null && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + n=$((n + 1)) + echo_i "checking display of non serial type zoneversion ($n)" + ret=0 + dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example >dig.out.test$n 2>&1 || ret=1 + grep '; ZONEVERSION: LABELS: 1, TYPE: 0, VALUE: 000007da ("....")' dig.out.test$n >/dev/null && ret=1 + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + + if [ $HAS_PYYAML -ne 0 ]; then + n=$((n + 1)) + echo_i "checking display of non serial type zoneversion +yaml ($n)" + ret=0 + dig_with_opts @10.53.0.2 +qr +ednsopt=ZONEVERSION:0100000007DA a.example +yaml >dig.out.test$n 2>&1 || ret=1 + $PYTHON yamlget.py dig.out.test$n 0 message query_message_data OPT_PSEUDOSECTION EDNS ZONEVERSION >yamlget.out.test$n 2>&1 || ret=1 + expected="{'ZONE': 'example', 'TYPE': 1, 'VALUE': 000007da, PVALUE: '....'}" + if [ $ret -ne 0 ]; then echo_i "failed"; fi + status=$((status + ret)) + fi + n=$((n + 1)) echo_i "check that dig gracefully handles bad escape in domain name ($n)" ret=0 diff --git a/bin/tests/system/resolver/ans2/ans.pl b/bin/tests/system/resolver/ans2/ans.pl index 079a7d9879..8ec4b0a59b 100644 --- a/bin/tests/system/resolver/ans2/ans.pl +++ b/bin/tests/system/resolver/ans2/ans.pl @@ -20,6 +20,8 @@ use IO::Socket; use Net::DNS; use Net::DNS::Packet; +print "Using Net::DNS $Net::DNS::VERSION\n"; + my $localport = int($ENV{'PORT'}); if (!$localport) { $localport = 5300; } @@ -170,6 +172,15 @@ for (;;) { $packet->push("authority", new Net::DNS::RR($qname . " 300 SOA . . 0 0 0 0 0")); } + } elsif ($qname eq "zoneversion") { + $packet->push("authority", new Net::DNS::RR(". 300 SOA . . 0 0 0 0 0")); + if ($Net::DNS::VERSION >= 1.49) { + $packet->edns->option('ZONEVERSION' => [0, 1, '01022304'] ) + } elsif ($Net::DNS::VERSION >= 1.35) { + $packet->edns->option('19' => {'BASE16' => '000101022304'} ) + } else { + $packet->edns->option('19' => pack 'H*', '000101022304') + } } else { # Data for the "bogus referrals" test $packet->push("authority", new Net::DNS::RR("below.www.example.com 300 NS ns.below.www.example.com")); diff --git a/bin/tests/system/resolver/ns1/named.conf.in b/bin/tests/system/resolver/ns1/named.conf.in index 23356889cc..cb0cb13d17 100644 --- a/bin/tests/system/resolver/ns1/named.conf.in +++ b/bin/tests/system/resolver/ns1/named.conf.in @@ -31,6 +31,7 @@ options { resolver-query-timeout 5000; # 5 seconds attach-cache "globalcache"; max-recursion-queries 100; + request-zoneversion yes; }; trust-anchors { }; diff --git a/bin/tests/system/resolver/ns5/named.conf.in b/bin/tests/system/resolver/ns5/named.conf.in index 32c5fa7111..21c5fe8735 100644 --- a/bin/tests/system/resolver/ns5/named.conf.in +++ b/bin/tests/system/resolver/ns5/named.conf.in @@ -26,6 +26,15 @@ options { querylog yes; prefetch 4 10; responselog yes; + request-nsid yes; + request-zoneversion yes; +}; + +// Don't break tests which depend on ans10 by requesting +// zoneversion or nsid +server 10.53.0.10 { + request-nsid no; + request-zoneversion no; }; include "trusted.conf"; diff --git a/bin/tests/system/resolver/tests.sh b/bin/tests/system/resolver/tests.sh index 01de8e7d7f..894f7c5266 100755 --- a/bin/tests/system/resolver/tests.sh +++ b/bin/tests/system/resolver/tests.sh @@ -901,6 +901,23 @@ test ${lines:-1} -ne 0 && ret=1 if [ $ret != 0 ]; then echo_i "failed"; fi status=$((status + ret)) +n=$((n + 1)) +echo_i "check that received ZONEVERSION is logged ($n)" +ret=0 +pat="received ZONEVERSION serial 2010 from 10.53.0.4#[0-9]* for mixedttl.tld/TXT zone tld" +grep "$pat" ns5/named.run >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "check that received ZONEVERSION is logged non serial ($n)" +ret=0 +dig_with_opts +tcp @10.53.0.1 zoneversion >dig.out.${n} || ret=1 +pat='received ZONEVERSION type 1 value 01022304 (\.\.#\.) from 10.53.0.2#[0-9]* for zoneversion/A zone \.' +grep "$pat" ns1/named.run >/dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + n=$((n + 1)) echo_i "check resolver behavior when FORMERR for EDNS options happens (${n})" ret=0 diff --git a/doc/arm/logging-categories.inc.rst b/doc/arm/logging-categories.inc.rst index 88d4f8b180..6fb0799df8 100644 --- a/doc/arm/logging-categories.inc.rst +++ b/doc/arm/logging-categories.inc.rst @@ -127,3 +127,7 @@ ``zoneload`` Loading of zones and creation of automatic empty zones. + +``zoneversion`` + ZONEVERSION options received from upstream servers. + diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 612fda7ad4..b36725487b 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -2165,6 +2165,14 @@ Boolean Options ultimate primary should be set to still send NOTIFY messages to all the name servers listed in the NS RRset. +.. namedconf:statement:: provide-zoneversion + :tags: transfer + :short: Controls the return EDNS ZONEVERSION answers. + + If ``yes`` EDNS ZONEVERSION answers will be returned otherwise + not for primary, secondary and mirror zones. The default is + ``yes``. + .. namedconf:statement:: recursion :tags: query :short: Defines whether recursion and caching are allowed. @@ -2188,6 +2196,18 @@ Boolean Options option in its response, then its contents are logged in the ``nsid`` category at level ``info``. The default is ``no``. +.. namedconf:statement:: request-zoneversion + :tags: query + :short: Controls whether an empty EDNS(0) ZONEVERSION option is sent with all queries to authoritative name servers during iterative resolution. + + If ``yes``, then an empty EDNS(0) ZONEVERSION option is sent + with all queries to authoritative name servers during iterative + resolution. If the authoritative server returns an ZONEVERSION + option in its response, then its contents are logged in the + ``zoneversion`` category at level ``info``. If the NSID has + also been requested and it is returned then that is appended to + the log message. The default is ``no``. + .. namedconf:statement:: require-cookie :tags: query :short: Controls whether responses without a server cookie are accepted. @@ -5601,11 +5621,13 @@ and :namedconf:ref:`options` blocks: - :namedconf:ref:`notify-source-v6` - :namedconf:ref:`notify-source` - :namedconf:ref:`provide-ixfr` + - :namedconf:ref:`provide-zoneversion` - :namedconf:ref:`query-source-v6` - :namedconf:ref:`query-source` - :namedconf:ref:`request-expire` - :namedconf:ref:`request-ixfr` - :namedconf:ref:`request-nsid` + - :namedconf:ref:`request-zoneversion` - :namedconf:ref:`require-cookie` - :namedconf:ref:`send-cookie` - :namedconf:ref:`transfer-format` diff --git a/doc/misc/mirror.zoneopt b/doc/misc/mirror.zoneopt index b1c5c08ea7..4db4d9f19b 100644 --- a/doc/misc/mirror.zoneopt +++ b/doc/misc/mirror.zoneopt @@ -33,6 +33,7 @@ zone [ ] { notify-source ( | * ); notify-source-v6 ( | * ); primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + provide-zoneversion ; request-expire ; request-ixfr ; request-ixfr-max-diffs ; diff --git a/doc/misc/options b/doc/misc/options index 3fa81f4bae..55f4afb3fa 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -226,6 +226,7 @@ options { preferred-glue ; prefetch [ ]; provide-ixfr ; + provide-zoneversion ; qname-minimization ( strict | relaxed | disabled | off ); query-source [ address ] ( | * | none ); query-source-v6 [ address ] ( | * | none ); @@ -254,6 +255,7 @@ options { request-ixfr ; request-ixfr-max-diffs ; request-nsid ; + request-zoneversion ; require-server-cookie ; resolver-query-timeout ; resolver-use-dns64 ; @@ -343,6 +345,7 @@ server { request-ixfr ; request-ixfr-max-diffs ; request-nsid ; + request-zoneversion ; require-cookie ; send-cookie ; tcp-keepalive ; @@ -509,6 +512,7 @@ view [ ] { preferred-glue ; prefetch [ ]; provide-ixfr ; + provide-zoneversion ; qname-minimization ( strict | relaxed | disabled | off ); query-source [ address ] ( | * | none ); query-source-v6 [ address ] ( | * | none ); @@ -534,6 +538,7 @@ view [ ] { request-ixfr ; request-ixfr-max-diffs ; request-nsid ; + request-zoneversion ; require-server-cookie ; resolver-query-timeout ; resolver-use-dns64 ; @@ -561,6 +566,7 @@ view [ ] { request-ixfr ; request-ixfr-max-diffs ; request-nsid ; + request-zoneversion ; require-cookie ; send-cookie ; tcp-keepalive ; diff --git a/doc/misc/primary.zoneopt b/doc/misc/primary.zoneopt index dfa2b79661..28d8dad453 100644 --- a/doc/misc/primary.zoneopt +++ b/doc/misc/primary.zoneopt @@ -51,6 +51,7 @@ zone [ ] { parental-agents [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; parental-source ( | * ); parental-source-v6 ( | * ); + provide-zoneversion ; send-report-channel ; serial-update-method ( date | increment | unixtime ); sig-signing-nodes ; diff --git a/doc/misc/secondary.zoneopt b/doc/misc/secondary.zoneopt index 6fbe1fbaf5..08c7008ca2 100644 --- a/doc/misc/secondary.zoneopt +++ b/doc/misc/secondary.zoneopt @@ -50,6 +50,7 @@ zone [ ] { parental-source ( | * ); parental-source-v6 ( | * ); primaries [ port ] [ source ( | * ) ] [ source-v6 ( | * ) ] { ( | [ port ] | [ port ] ) [ key ] [ tls ]; ... }; + provide-zoneversion ; request-expire ; request-ixfr ; request-ixfr-max-diffs ; diff --git a/lib/dns/db.c b/lib/dns/db.c index 90ac276bc5..a623dbaf92 100644 --- a/lib/dns/db.c +++ b/lib/dns/db.c @@ -1200,3 +1200,14 @@ dns__db_logtoomanyrecords(dns_db_t *db, const dns_name_t *name, (db->attributes & DNS_DBATTR_CACHE) != 0 ? "cache" : "zone", isc_result_totext(DNS_R_TOOMANYRECORDS), limit); } + +isc_result_t +dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b) { + REQUIRE(db != NULL); + REQUIRE(b != NULL); + + if (db->methods->getzoneversion != NULL) { + return (db->methods->getzoneversion)(db, b); + } + return ISC_R_NOTIMPLEMENTED; +} diff --git a/lib/dns/include/dns/db.h b/lib/dns/include/dns/db.h index fd8ab3323a..e53f8a92e8 100644 --- a/lib/dns/include/dns/db.h +++ b/lib/dns/include/dns/db.h @@ -182,6 +182,7 @@ typedef struct dns_dbmethods { dns_name_t *name); void (*setmaxrrperset)(dns_db_t *db, uint32_t value); void (*setmaxtypepername)(dns_db_t *db, uint32_t value); + isc_result_t (*getzoneversion)(dns_db_t *db, isc_buffer_t *b); } dns_dbmethods_t; typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx, @@ -1805,3 +1806,21 @@ dns_db_setmaxtypepername(dns_db_t *db, uint32_t value); * stored at a given node, then any subsequent attempt to add an rdataset * with a new RR type will return ISC_R_TOOMANYRECORDS. */ + +isc_result_t +dns_db_getzoneversion(dns_db_t *db, isc_buffer_t *b); +/*%< + * Provides a database specific EDNS ZONEVERSION option. + * + * Requires: + * \li 'db' is a valid database + * \li 'b' is a valid buffer + * + * Returns: + * \li ISC_R_SUCCESS when it has populated the buffer with the ZONEVERSION + * response (maybe empty implying no ZONEVERSION to be returned). + * \li ISC_R_NOSPACE if the buffer is too small. + * \li ISC_R_NOTIMPLEMENTED if there is not a database specific + * ZONEVERSION + * \li ISC_R_FAILURE other failures + */ diff --git a/lib/dns/include/dns/message.h b/lib/dns/include/dns/message.h index a467ea447f..178a809ef2 100644 --- a/lib/dns/include/dns/message.h +++ b/lib/dns/include/dns/message.h @@ -102,7 +102,6 @@ #define DNS_MESSAGEEXTFLAG_DO 0x8000U /*%< EDNS0 extended OPT codes */ - #define DNS_OPT_LLQ 1 /*%< LLQ opt code */ #define DNS_OPT_UL 2 /*%< UL opt code */ #define DNS_OPT_NSID 3 /*%< NSID opt code */ @@ -129,7 +128,7 @@ * options we know about. Extended DNS Errors may occur multiple times, see * DNS_EDE_MAX_ERRORS. */ -#define DNS_EDNSOPTIONS 8 + DNS_EDE_MAX_ERRORS +#define DNS_EDNSOPTIONS 9 + DNS_EDE_MAX_ERRORS #define DNS_MESSAGE_REPLYPRESERVE (DNS_MESSAGEFLAG_RD | DNS_MESSAGEFLAG_CD) #define DNS_MESSAGEEXTFLAG_REPLYPRESERVE (DNS_MESSAGEEXTFLAG_DO) diff --git a/lib/dns/include/dns/peer.h b/lib/dns/include/dns/peer.h index e2bcc66725..213bdf0cc6 100644 --- a/lib/dns/include/dns/peer.h +++ b/lib/dns/include/dns/peer.h @@ -115,6 +115,12 @@ dns_peer_setrequestnsid(dns_peer_t *peer, bool newval); isc_result_t dns_peer_getrequestnsid(dns_peer_t *peer, bool *retval); +isc_result_t +dns_peer_setrequestzoneversion(dns_peer_t *peer, bool newval); + +isc_result_t +dns_peer_getrequestzoneversion(dns_peer_t *peer, bool *retval); + isc_result_t dns_peer_setsendcookie(dns_peer_t *peer, bool newval); diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 7bedc07ee2..0b00df679c 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -105,31 +105,32 @@ typedef enum { dns_quotatype_zone = 0, dns_quotatype_server } dns_quotatype_t; * Options that modify how a 'fetch' is done. */ enum { - DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */ - DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */ - DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */ - DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */ - DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */ - DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */ - DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */ - DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */ - DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */ - DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */ - DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */ - DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */ - DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset - * from the child zone when a - * delegation is returned in - * response to a NS query. */ - DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers - * that return errors on - * non-empty terminals. */ - DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when - * doing qname minimization - * on ip6.arpa. */ - DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if - * possible. */ - DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */ + DNS_FETCHOPT_TCP = 1 << 0, /*%< Use TCP. */ + DNS_FETCHOPT_UNSHARED = 1 << 1, /*%< See below. */ + DNS_FETCHOPT_RECURSIVE = 1 << 2, /*%< Set RD? */ + DNS_FETCHOPT_NOEDNS0 = 1 << 3, /*%< Do not use EDNS. */ + DNS_FETCHOPT_FORWARDONLY = 1 << 4, /*%< Only use forwarders. */ + DNS_FETCHOPT_NOVALIDATE = 1 << 5, /*%< Disable validation. */ + DNS_FETCHOPT_WANTNSID = 1 << 6, /*%< Request NSID */ + DNS_FETCHOPT_PREFETCH = 1 << 7, /*%< Do prefetch */ + DNS_FETCHOPT_NOCDFLAG = 1 << 8, /*%< Don't set CD flag. */ + DNS_FETCHOPT_NONTA = 1 << 9, /*%< Ignore NTA table. */ + DNS_FETCHOPT_NOCACHED = 1 << 10, /*%< Force cache update. */ + DNS_FETCHOPT_QMINIMIZE = 1 << 11, /*%< Use qname minimization. */ + DNS_FETCHOPT_NOFOLLOW = 1 << 12, /*%< Don't retrieve the NS RRset + * from the child zone when a + * delegation is returned in + * response to a NS query. */ + DNS_FETCHOPT_QMIN_STRICT = 1 << 13, /*%< Do not work around servers + * that return errors on + * non-empty terminals. */ + DNS_FETCHOPT_QMIN_SKIP_IP6A = 1 << 14, /*%< Skip some labels when + * doing qname minimization + * on ip6.arpa. */ + DNS_FETCHOPT_NOFORWARD = 1 << 15, /*%< Do not use forwarders if + * possible. */ + DNS_FETCHOPT_QMINFETCH = 1 << 16, /*%< Qmin fetch */ + DNS_FETCHOPT_WANTZONEVERSION = 1 << 17, /*%< Request ZONEVERSION */ /*% EDNS version bits: */ DNS_FETCHOPT_EDNSVERSIONSET = 1 << 23, diff --git a/lib/dns/include/dns/view.h b/lib/dns/include/dns/view.h index 266ba49d6c..4cd2f66ad1 100644 --- a/lib/dns/include/dns/view.h +++ b/lib/dns/include/dns/view.h @@ -145,6 +145,7 @@ struct dns_view { dns_rrl_t *rrl; bool provideixfr; bool requestnsid; + bool requestzoneversion; bool sendcookie; dns_ttl_t maxcachettl; dns_ttl_t maxncachettl; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index b000f27e10..a9d8526a7f 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -102,6 +102,7 @@ typedef enum { DNS_ZONEOPT_CHECKTTL = 1 << 28, /*%< check max-zone-ttl */ DNS_ZONEOPT_AUTOEMPTY = 1 << 29, /*%< automatic empty zone */ DNS_ZONEOPT_CHECKSVCB = 1 << 30, /*%< check SVBC records */ + DNS_ZONEOPT_ZONEVERSION = 1U << 31, /*%< enable zoneversion */ DNS_ZONEOPT___MAX = UINT64_MAX, /* trick to make the ENUM 64-bit wide */ } dns_zoneopt_t; @@ -2785,6 +2786,24 @@ dns_zone_getrad(dns_zone_t *zone, dns_name_t *name); * \li 'name' is a valid name with a buffer. */ +isc_result_t +dns_zone_getzoneversion(dns_zone_t *zone, isc_buffer_t *b); +/**< + * Return the EDNS ZONEVERSION for this zone. + * + * Note: For type SERIAL a buffer of at least 6 octets is required. + * + * Requires: + * \li 'zone' to be a valid zone. + * \li 'b' to be a valid buffer. + * + * Returns + * \li ISC_R_SUCCESS if the zone is loaded and supports ZONEVERSION. + * \li ISC_R_NOSPACE if the buffer is too small. + * \li DNS_R_NOTLOADED if the database is not loaded. + * \li ISC_R_FAILURE other failure. + */ + #if DNS_ZONE_TRACE #define dns_zone_ref(ptr) dns_zone__ref(ptr, __func__, __FILE__, __LINE__) #define dns_zone_unref(ptr) dns_zone__unref(ptr, __func__, __FILE__, __LINE__) diff --git a/lib/dns/message.c b/lib/dns/message.c index caeff02f4f..42ce913162 100644 --- a/lib/dns/message.c +++ b/lib/dns/message.c @@ -3512,6 +3512,116 @@ static const char *option_names[] = { [DNS_OPT_ZONEVERSION] = "ZONEVERSION", }; +static isc_result_t +render_zoneversion(dns_message_t *msg, isc_buffer_t *optbuf, + const dns_master_style_t *style, isc_buffer_t *target) { + isc_result_t result = ISC_R_SUCCESS; + unsigned int labels = isc_buffer_getuint8(optbuf); + unsigned int type = isc_buffer_getuint8(optbuf); + char buf[sizeof("4000000000")]; + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_t *name = ISC_LIST_HEAD(msg->sections[DNS_SECTION_QUESTION]); + dns_name_t suffix = DNS_NAME_INITEMPTY; + bool yaml = false, rawmode = false; + const char *sep1 = " ", *sep2 = ", "; + + if ((dns_master_styleflags(style) & DNS_STYLEFLAG_YAML) != 0) { + msg->indent.count++; + sep1 = sep2 = "\n"; + yaml = true; + } + + ADD_STRING(target, sep1); + + if (msg->counts[DNS_SECTION_QUESTION] != 1 || name == NULL || + dns_name_countlabels(name) < labels + 1) + { + rawmode = true; + INDENT(style); + ADD_STRING(target, "LABELS: "); + snprintf(buf, sizeof(buf), "%u", labels); + ADD_STRING(target, buf); + } else { + dns_name_split(name, labels + 1, NULL, &suffix); + dns_name_format(&suffix, namebuf, sizeof(namebuf)); + + INDENT(style); + ADD_STRING(target, "ZONE: "); + if (yaml) { + char *s = namebuf; + ADD_STRING(target, "\""); + while (*s != 0) { + if (*s == '\\' || *s == '"') { + ADD_STRING(target, "\\"); + } + if (isc_buffer_availablelength(target) < 1) { + result = ISC_R_NOSPACE; + goto cleanup; + } + isc_buffer_putmem(target, (unsigned char *)s, + 1); + s++; + } + ADD_STRING(target, "\""); + } else { + ADD_STRING(target, namebuf); + } + } + ADD_STRING(target, sep2); + + if (!rawmode && type == 0 && isc_buffer_remaininglength(optbuf) == 4) { + uint32_t serial = isc_buffer_getuint32(optbuf); + INDENT(style); + ADD_STRING(target, "SOA-SERIAL: "); + snprintf(buf, sizeof(buf), "%u", serial); + ADD_STRING(target, buf); + } else { + size_t len = isc_buffer_remaininglength(optbuf); + unsigned char *data = isc_buffer_current(optbuf); + INDENT(style); + ADD_STRING(target, "TYPE: "); + snprintf(buf, sizeof(buf), "%u", type); + ADD_STRING(target, buf); + ADD_STRING(target, sep2); + INDENT(style); + ADD_STRING(target, "VALUE: "); + for (size_t i = 0; i < len; i++) { + snprintf(buf, sizeof(buf), "%02x", data[i]); + ADD_STRING(target, buf); + } + if (yaml) { + ADD_STRING(target, sep2); + INDENT(style); + ADD_STRING(target, "PVALUE: \""); + } else { + ADD_STRING(target, " (\""); + } + for (size_t i = 0; i < len; i++) { + if (isprint(data[i])) { + if (yaml && (data[i] == '\\' || data[i] == '"')) + { + ADD_STRING(target, "\\"); + } + if (isc_buffer_availablelength(target) < 1) { + result = ISC_R_NOSPACE; + goto cleanup; + } + isc_buffer_putmem(target, &data[i], 1); + } else { + ADD_STRING(target, "."); + } + } + if (yaml) { + ADD_STRING(target, "\""); + } else { + ADD_STRING(target, "\")"); + } + isc_buffer_forward(optbuf, len); + } +cleanup: + return result; +} + static isc_result_t dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, const dns_master_style_t *style, @@ -3789,6 +3899,20 @@ dns_message_pseudosectiontoyaml(dns_message_t *msg, dns_pseudosection_t section, optbuf = sb; } break; + case DNS_OPT_ZONEVERSION: + if (optlen >= 2U) { + isc_buffer_t zonebuf = optbuf; + isc_buffer_setactive(&zonebuf, optlen); + result = render_zoneversion( + msg, &zonebuf, style, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + isc_buffer_forward(&optbuf, optlen); + ADD_STRING(target, "\n"); + continue; + } + break; default: break; } @@ -4208,6 +4332,20 @@ dns_message_pseudosectiontotext(dns_message_t *msg, dns_pseudosection_t section, optbuf = sb; } break; + case DNS_OPT_ZONEVERSION: + if (optlen >= 2U) { + isc_buffer_t zonebuf = optbuf; + isc_buffer_setactive(&zonebuf, optlen); + result = render_zoneversion( + msg, &zonebuf, style, target); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + ADD_STRING(target, "\n"); + isc_buffer_forward(&optbuf, optlen); + continue; + } + break; default: break; } diff --git a/lib/dns/peer.c b/lib/dns/peer.c index f633ab285f..6f011d26de 100644 --- a/lib/dns/peer.c +++ b/lib/dns/peer.c @@ -57,6 +57,7 @@ struct dns_peer { bool request_ixfr; bool support_edns; bool request_nsid; + bool request_zoneversion; bool send_cookie; bool require_cookie; bool request_expire; @@ -98,7 +99,8 @@ enum { SERVER_PADDING_BIT, REQUEST_TCP_KEEPALIVE_BIT, REQUIRE_COOKIE_BIT, - DNS_PEER_FLAGS_COUNT + DNS_PEER_FLAGS_COUNT, + REQUEST_ZONEVERSION }; STATIC_ASSERT(DNS_PEER_FLAGS_COUNT <= CHAR_BIT * sizeof(uint32_t), @@ -382,6 +384,8 @@ ACCESS_OPTION(requestixfr, REQUEST_IXFR_BIT, bool, request_ixfr) ACCESS_OPTION(requestixfrmaxdiffs, REQUEST_IXFRMAXDIFFS_BIT, uint32_t, request_ixfr_maxdiffs) ACCESS_OPTION(requestnsid, REQUEST_NSID_BIT, bool, request_nsid) +ACCESS_OPTION(requestzoneversion, REQUEST_ZONEVERSION, bool, + request_zoneversion) ACCESS_OPTION(requirecookie, REQUIRE_COOKIE_BIT, bool, require_cookie) ACCESS_OPTION(sendcookie, SEND_COOKIE_BIT, bool, send_cookie) ACCESS_OPTION(supportedns, SUPPORT_EDNS_BIT, bool, support_edns) diff --git a/lib/dns/rdata/generic/opt_41.c b/lib/dns/rdata/generic/opt_41.c index 5c81b1546c..b52288dc9b 100644 --- a/lib/dns/rdata/generic/opt_41.c +++ b/lib/dns/rdata/generic/opt_41.c @@ -266,6 +266,21 @@ fromwire_opt(ARGS_FROMWIRE) { } isc_region_consume(&sregion, length); break; + case DNS_OPT_ZONEVERSION: + if (length == 0) { + /* Request */ + break; + } + /* Labels and Type */ + if (length < 2) { + return DNS_R_OPTERR; + } + /* Type 0 (serial), length is 6. */ + if (sregion.base[1] == 0 && length != 6) { + return DNS_R_OPTERR; + } + isc_region_consume(&sregion, length); + break; default: isc_region_consume(&sregion, length); break; diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 410141f052..aa3050b8a6 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2504,6 +2505,7 @@ resquery_send(resquery_t *query) { unsigned int flags = query->addrinfo->flags; bool reqnsid = res->view->requestnsid; bool sendcookie = res->view->sendcookie; + bool reqzoneversion = res->view->requestzoneversion; bool tcpkeepalive = false; unsigned char cookie[COOKIE_BUFFER_SIZE]; uint16_t padding = 0; @@ -2546,8 +2548,6 @@ resquery_send(resquery_t *query) { */ if (peer != NULL) { uint8_t ednsversion; - (void)dns_peer_getrequestnsid(peer, &reqnsid); - (void)dns_peer_getsendcookie(peer, &sendcookie); result = dns_peer_getednsversion(peer, &ednsversion); if (result == ISC_R_SUCCESS && @@ -2555,6 +2555,10 @@ resquery_send(resquery_t *query) { { version = ednsversion; } + (void)dns_peer_getrequestnsid(peer, &reqnsid); + (void)dns_peer_getrequestzoneversion( + peer, &reqzoneversion); + (void)dns_peer_getsendcookie(peer, &sendcookie); } if (NOCOOKIE(query->addrinfo)) { sendcookie = false; @@ -2566,6 +2570,13 @@ resquery_send(resquery_t *query) { ednsopts[ednsopt].value = NULL; ednsopt++; } + if (reqzoneversion) { + INSIST(ednsopt < DNS_EDNSOPTIONS); + ednsopts[ednsopt].code = DNS_OPT_ZONEVERSION; + ednsopts[ednsopt].length = 0; + ednsopts[ednsopt].value = NULL; + ednsopt++; + } if (sendcookie) { INSIST(ednsopt < DNS_EDNSOPTIONS); ednsopts[ednsopt].code = DNS_OPT_COOKIE; @@ -2620,8 +2631,14 @@ resquery_send(resquery_t *query) { query->ednsversion = version; result = fctx_addopt(fctx->qmessage, version, udpsize, ednsopts, ednsopt); - if (reqnsid && result == ISC_R_SUCCESS) { - query->options |= DNS_FETCHOPT_WANTNSID; + if (result == ISC_R_SUCCESS) { + if (reqnsid) { + query->options |= DNS_FETCHOPT_WANTNSID; + } + if (reqzoneversion) { + query->options |= + DNS_FETCHOPT_WANTZONEVERSION; + } } else if (result != ISC_R_SUCCESS) { /* * We couldn't add the OPT, but we'll @@ -7387,17 +7404,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); @@ -7407,20 +7445,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)); @@ -7431,6 +7459,108 @@ log_nsid(isc_buffer_t *opt, size_t nsid_len, resquery_t *query, int level, isc_mem_put(mctx, buf, buflen); } +static void +log_zoneversion(unsigned char *version, size_t version_len, unsigned char *nsid, + size_t nsid_len, resquery_t *query, int level, + isc_mem_t *mctx) { + char addrbuf[ISC_SOCKADDR_FORMATSIZE]; + char namebuf[DNS_NAME_FORMATSIZE]; + size_t nsid_buflen = 0; + char *nsid_buf = NULL; + char *nsid_pbuf = NULL; + const char *nsid_hex = ""; + const char *nsid_print = ""; + const char *sep_1 = ""; + const char *sep_2 = ""; + const char *sep_3 = ""; + dns_name_t suffix = DNS_NAME_INITEMPTY; + unsigned int labels; + + REQUIRE(version_len <= UINT16_MAX); + + /* + * Don't log reflected ZONEVERSION option. + */ + if (version_len == 0) { + return; + } + + /* Enforced by dns_rdata_fromwire. */ + INSIST(version_len >= 2); + + /* + * Sanity check on label count. + */ + labels = version[0] + 1; + if (dns_name_countlabels(query->fctx->name) < labels) { + return; + } + + /* + * Get zone name. + */ + dns_name_split(query->fctx->name, labels, NULL, &suffix); + dns_name_format(&suffix, namebuf, sizeof(namebuf)); + + if (nsid != NULL) { + nsid_buflen = nsid_len * 2 + 1; + nsid_hex = nsid_buf = isc_mem_get(mctx, nsid_buflen); + nsid_print = nsid_pbuf = isc_mem_get(mctx, nsid_len + 1); + + /* Convert to hex */ + make_hex(nsid, nsid_len, nsid_buf, nsid_buflen); + + /* Convert to printable */ + make_printable(nsid, nsid_len, nsid_pbuf, nsid_len + 1); + + sep_1 = " (NSID "; + sep_2 = " ("; + sep_3 = "))"; + } + + isc_sockaddr_format(&query->addrinfo->sockaddr, addrbuf, + sizeof(addrbuf)); + if (version[1] == 0 && version_len == 6) { + uint32_t serial = version[2] << 24 | version[3] << 2 | + version[4] << 8 | version[5]; + isc_log_write(DNS_LOGCATEGORY_ZONEVERSION, + DNS_LOGMODULE_RESOLVER, level, + "received ZONEVERSION serial %u from %s for %s " + "zone %s%s%s%s%s%s", + serial, addrbuf, query->fctx->info, namebuf, + sep_1, nsid_hex, sep_2, nsid_print, sep_3); + } else { + size_t version_buflen = version_len * 2 + 1; + char *version_hex = isc_mem_get(mctx, version_buflen); + char *version_pbuf = isc_mem_get(mctx, version_len - 1); + + /* Convert to hex */ + make_hex(version + 2, version_len - 2, version_hex, + version_buflen); + + /* Convert to printable */ + make_printable(version + 2, version_len - 2, version_pbuf, + version_len - 1); + + isc_log_write(DNS_LOGCATEGORY_ZONEVERSION, + DNS_LOGMODULE_RESOLVER, level, + "received ZONEVERSION type %u value %s (%s) from " + "%s for %s zone %s%s%s%s%s%s", + version[1], version_hex, version_pbuf, addrbuf, + query->fctx->info, namebuf, sep_1, nsid_hex, + sep_2, nsid_print, sep_3); + isc_mem_put(mctx, version_hex, version_buflen); + isc_mem_put(mctx, version_pbuf, version_len - 1); + } + + if (nsid_pbuf != NULL) { + isc_mem_put(mctx, nsid_pbuf, nsid_len + 1); + } + if (nsid_buf != NULL) { + isc_mem_put(mctx, nsid_buf, nsid_buflen); + } +} + static bool betterreferral(respctx_t *rctx) { isc_result_t result; @@ -8191,6 +8321,11 @@ rctx_opt(respctx_t *rctx) { isc_result_t result; bool seen_cookie = false; bool seen_nsid = false; + bool seen_zoneversion = false; + unsigned char *nsid = NULL; + uint16_t nsidlen = 0; + unsigned char *zoneversion = NULL; + uint16_t zoneversionlen = 0; result = dns_rdataset_first(rctx->opt); if (result != ISC_R_SUCCESS) { @@ -8216,7 +8351,8 @@ rctx_opt(respctx_t *rctx) { break; } seen_nsid = true; - + nsid = isc_buffer_current(&optbuf); + nsidlen = optlen; if ((query->options & DNS_FETCHOPT_WANTNSID) != 0) { log_nsid(&optbuf, optlen, query, ISC_LOG_INFO, fctx->mctx); @@ -8254,12 +8390,27 @@ rctx_opt(respctx_t *rctx) { optvalue, optlen); } break; + case DNS_OPT_ZONEVERSION: + if (seen_zoneversion) { + break; + } + seen_zoneversion = true; + zoneversion = isc_buffer_current(&optbuf); + zoneversionlen = optlen; + break; default: break; } isc_buffer_forward(&optbuf, optlen); } INSIST(isc_buffer_remaininglength(&optbuf) == 0U); + + if ((query->options & DNS_FETCHOPT_WANTZONEVERSION) != 0 && + zoneversion != NULL) + { + log_zoneversion(zoneversion, zoneversionlen, nsid, nsidlen, + query, ISC_LOG_INFO, fctx->mctx); + } } /* diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 1ec93ef51c..96e642f957 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -1497,6 +1497,56 @@ dns_zone_getserial(dns_zone_t *zone, uint32_t *serialp) { return result; } +isc_result_t +dns_zone_getzoneversion(dns_zone_t *zone, isc_buffer_t *b) { + isc_result_t result = DNS_R_NOTLOADED; + unsigned int soacount; + uint32_t serial; + dns_zone_t *mayberaw = zone; + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(b != NULL); + + LOCK_ZONE(zone); + if (zone->raw != NULL) { + LOCK_ZONE(zone->raw); + mayberaw = zone->raw; + } + ZONEDB_LOCK(&mayberaw->dblock, isc_rwlocktype_read); + if (DNS_ZONE_OPTION(mayberaw, DNS_ZONEOPT_ZONEVERSION) && + mayberaw->db != NULL) + { + result = dns_db_getzoneversion(mayberaw->db, b); + if (result == ISC_R_NOTIMPLEMENTED) { + result = zone_get_from_db(mayberaw, mayberaw->db, NULL, + &soacount, NULL, &serial, + NULL, NULL, NULL, NULL, NULL); + if (result == ISC_R_SUCCESS && soacount == 0) { + result = ISC_R_FAILURE; + } + if (result == ISC_R_SUCCESS) { + if (isc_buffer_availablelength(b) >= 6) { + isc_buffer_putuint8( + b, dns_name_countlabels( + &mayberaw->origin) - + 1); + isc_buffer_putuint8(b, 0); + isc_buffer_putuint32(b, serial); + } else { + result = ISC_R_NOSPACE; + } + } + } + } + ZONEDB_UNLOCK(&mayberaw->dblock, isc_rwlocktype_read); + if (zone->raw != NULL) { + UNLOCK_ZONE(zone->raw); + } + UNLOCK_ZONE(zone); + + return result; +} + /* * Single shot. */ diff --git a/lib/isc/include/isc/log.h b/lib/isc/include/isc/log.h index ff954be1ba..a693be7cc1 100644 --- a/lib/isc/include/isc/log.h +++ b/lib/isc/include/isc/log.h @@ -119,36 +119,37 @@ enum isc_logcategory { NAMED_LOGCATEGORY_GENERAL = ISC_LOGCATEGORY_GENERAL, ISC_LOGCATEGORY_SSLKEYLOG, /* dns categories */ - DNS_LOGCATEGORY_NOTIFY, + DNS_LOGCATEGORY_CNAME, DNS_LOGCATEGORY_DATABASE, - DNS_LOGCATEGORY_SECURITY, + DNS_LOGCATEGORY_DISPATCH, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGCATEGORY_DNSTAP, + DNS_LOGCATEGORY_EDNS_DISABLED, + DNS_LOGCATEGORY_LAME_SERVERS, + DNS_LOGCATEGORY_NOTIFY, + DNS_LOGCATEGORY_NSID, DNS_LOGCATEGORY_RESOLVER, + DNS_LOGCATEGORY_RPZ, + DNS_LOGCATEGORY_RPZ_PASSTHRU, + DNS_LOGCATEGORY_RRL, + DNS_LOGCATEGORY_SECURITY, + DNS_LOGCATEGORY_SPILL, + DNS_LOGCATEGORY_UPDATE_POLICY, DNS_LOGCATEGORY_XFER_IN, DNS_LOGCATEGORY_XFER_OUT, - DNS_LOGCATEGORY_DISPATCH, - DNS_LOGCATEGORY_LAME_SERVERS, - DNS_LOGCATEGORY_EDNS_DISABLED, - DNS_LOGCATEGORY_RPZ, - DNS_LOGCATEGORY_RRL, - DNS_LOGCATEGORY_CNAME, - DNS_LOGCATEGORY_SPILL, - DNS_LOGCATEGORY_DNSTAP, DNS_LOGCATEGORY_ZONELOAD, - DNS_LOGCATEGORY_NSID, - DNS_LOGCATEGORY_RPZ_PASSTHRU, - DNS_LOGCATEGORY_UPDATE_POLICY, + DNS_LOGCATEGORY_ZONEVERSION, /* ns categories */ NS_LOGCATEGORY_CLIENT, - NS_LOGCATEGORY_NETWORK, - NS_LOGCATEGORY_UPDATE, - NS_LOGCATEGORY_QUERIES, - NS_LOGCATEGORY_UPDATE_SECURITY, - NS_LOGCATEGORY_QUERY_ERRORS, - NS_LOGCATEGORY_TAT, - NS_LOGCATEGORY_SERVE_STALE, - NS_LOGCATEGORY_RESPONSES, NS_LOGCATEGORY_DRA, + NS_LOGCATEGORY_NETWORK, + NS_LOGCATEGORY_QUERIES, + NS_LOGCATEGORY_QUERY_ERRORS, + NS_LOGCATEGORY_RESPONSES, + NS_LOGCATEGORY_SERVE_STALE, + NS_LOGCATEGORY_TAT, + NS_LOGCATEGORY_UPDATE, + NS_LOGCATEGORY_UPDATE_SECURITY, /* cfg categories */ CFG_LOGCATEGORY_CONFIG, /* named categories */ diff --git a/lib/isc/log.c b/lib/isc/log.c index 79dbe3cf6d..576e6b4150 100644 --- a/lib/isc/log.c +++ b/lib/isc/log.c @@ -170,36 +170,37 @@ static const char *categories_description[] = { [ISC_LOGCATEGORY_GENERAL] = "general", [ISC_LOGCATEGORY_SSLKEYLOG] = "sslkeylog", /* dns categories */ - [DNS_LOGCATEGORY_NOTIFY] = "notify", + [DNS_LOGCATEGORY_CNAME] = "cname", [DNS_LOGCATEGORY_DATABASE] = "database", - [DNS_LOGCATEGORY_SECURITY] = "security", + [DNS_LOGCATEGORY_DISPATCH] = "dispatch", [DNS_LOGCATEGORY_DNSSEC] = "dnssec", + [DNS_LOGCATEGORY_DNSTAP] = "dnstap", + [DNS_LOGCATEGORY_EDNS_DISABLED] = "edns-disabled", + [DNS_LOGCATEGORY_LAME_SERVERS] = "lame-servers", + [DNS_LOGCATEGORY_NOTIFY] = "notify", + [DNS_LOGCATEGORY_NSID] = "nsid", [DNS_LOGCATEGORY_RESOLVER] = "resolver", + [DNS_LOGCATEGORY_RPZ] = "rpz", + [DNS_LOGCATEGORY_RPZ_PASSTHRU] = "rpz-passthru", + [DNS_LOGCATEGORY_RRL] = "rate-limit", + [DNS_LOGCATEGORY_SECURITY] = "security", + [DNS_LOGCATEGORY_SPILL] = "spill", + [DNS_LOGCATEGORY_UPDATE_POLICY] = "update-policy", [DNS_LOGCATEGORY_XFER_IN] = "xfer-in", [DNS_LOGCATEGORY_XFER_OUT] = "xfer-out", - [DNS_LOGCATEGORY_DISPATCH] = "dispatch", - [DNS_LOGCATEGORY_LAME_SERVERS] = "lame-servers", - [DNS_LOGCATEGORY_EDNS_DISABLED] = "edns-disabled", - [DNS_LOGCATEGORY_RPZ] = "rpz", - [DNS_LOGCATEGORY_RRL] = "rate-limit", - [DNS_LOGCATEGORY_CNAME] = "cname", - [DNS_LOGCATEGORY_SPILL] = "spill", - [DNS_LOGCATEGORY_DNSTAP] = "dnstap", [DNS_LOGCATEGORY_ZONELOAD] = "zoneload", - [DNS_LOGCATEGORY_NSID] = "nsid", - [DNS_LOGCATEGORY_RPZ_PASSTHRU] = "rpz-passthru", - [DNS_LOGCATEGORY_UPDATE_POLICY] = "update-policy", + [DNS_LOGCATEGORY_ZONEVERSION] = "zoneversion", /* ns categories */ [NS_LOGCATEGORY_CLIENT] = "client", - [NS_LOGCATEGORY_NETWORK] = "network", - [NS_LOGCATEGORY_UPDATE] = "update", - [NS_LOGCATEGORY_QUERIES] = "queries", - [NS_LOGCATEGORY_UPDATE_SECURITY] = "update-security", - [NS_LOGCATEGORY_QUERY_ERRORS] = "query-errors", - [NS_LOGCATEGORY_TAT] = "trust-anchor-telemetry", [NS_LOGCATEGORY_DRA] = "dns-reporting-agent", - [NS_LOGCATEGORY_SERVE_STALE] = "serve-stale", + [NS_LOGCATEGORY_NETWORK] = "network", + [NS_LOGCATEGORY_QUERIES] = "queries", + [NS_LOGCATEGORY_QUERY_ERRORS] = "query-errors", [NS_LOGCATEGORY_RESPONSES] = "responses", + [NS_LOGCATEGORY_SERVE_STALE] = "serve-stale", + [NS_LOGCATEGORY_TAT] = "trust-anchor-telemetry", + [NS_LOGCATEGORY_UPDATE] = "update", + [NS_LOGCATEGORY_UPDATE_SECURITY] = "update-security", /* cfg categories */ [CFG_LOGCATEGORY_CONFIG] = "config", /* named categories */ diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 2e8a4b2297..3df96f1b33 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -4242,6 +4242,7 @@ static struct { { "request-expire", dns_peer_setrequestexpire }, { "request-ixfr", dns_peer_setrequestixfr }, { "request-nsid", dns_peer_setrequestnsid }, + { "request-zoneversion", dns_peer_setrequestzoneversion }, { "send-cookie", dns_peer_setsendcookie }, { "tcp-keepalive", dns_peer_settcpkeepalive }, { "tcp-only", dns_peer_setforcetcp }, diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 85d1c531c0..72b317db15 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -2110,6 +2110,7 @@ static cfg_clausedef_t view_clauses[] = { { "recursion", &cfg_type_boolean, 0 }, { "request-nsid", &cfg_type_boolean, 0 }, { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT }, + { "request-zoneversion", &cfg_type_boolean, 0 }, { "require-server-cookie", &cfg_type_boolean, 0 }, { "resolver-nonbackoff-tries", NULL, CFG_CLAUSEFLAG_ANCIENT }, { "resolver-query-timeout", &cfg_type_uint32, 0 }, @@ -2354,6 +2355,8 @@ static cfg_clausedef_t zone_clauses[] = { CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, { "parental-source-v6", &cfg_type_sockaddr6wild, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, + { "provide-zoneversion", &cfg_type_boolean, + CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR }, { "send-report-channel", &cfg_type_astring, CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY }, { "request-expire", &cfg_type_boolean, @@ -2603,6 +2606,7 @@ static cfg_clausedef_t server_clauses[] = { { "request-ixfr", &cfg_type_boolean, 0 }, { "request-ixfr-max-diffs", &cfg_type_uint32, 0 }, { "request-nsid", &cfg_type_boolean, 0 }, + { "request-zoneversion", &cfg_type_boolean, 0 }, { "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT }, { "require-cookie", &cfg_type_boolean, 0 }, { "send-cookie", &cfg_type_boolean, 0 }, diff --git a/lib/ns/client.c b/lib/ns/client.c index 8420dcacc0..e1cdd3492d 100644 --- a/lib/ns/client.c +++ b/lib/ns/client.c @@ -102,6 +102,8 @@ #define WANTNSID(x) (((x)->attributes & NS_CLIENTATTR_WANTNSID) != 0) #define WANTPAD(x) (((x)->attributes & NS_CLIENTATTR_WANTPAD) != 0) #define WANTRC(x) (((x)->attributes & NS_CLIENTATTR_WANTRC) != 0) +#define WANTZONEVERSION(x) \ + (((x)->attributes & NS_CLIENTATTR_WANTZONEVERSION) != 0) #define MANAGER_MAGIC ISC_MAGIC('N', 'S', 'C', 'm') #define VALID_MANAGER(m) ISC_MAGIC_VALID(m, MANAGER_MAGIC) @@ -218,6 +220,17 @@ ns_client_settimeout(ns_client_t *client, unsigned int seconds) { /* XXXWPK TODO use netmgr to set timeout */ } +static void +client_zoneversion_reset(ns_client_t *client) { + if (client->zoneversion == NULL) { + return; + } + isc_mem_put(client->manager->mctx, client->zoneversion, + client->zoneversionlength); + client->zoneversion = NULL; + client->zoneversionlength = 0; +} + static void ns_client_endrequest(ns_client_t *client) { INSIST(client->state == NS_CLIENTSTATE_WORKING || @@ -258,6 +271,7 @@ ns_client_endrequest(ns_client_t *client) { dns_message_puttemprdataset(client->message, &client->opt); } + client_zoneversion_reset(client); client->signer = NULL; client->udpsize = 512; client->extflags = 0; @@ -1192,6 +1206,13 @@ no_nsid: ednsopts[count].value = ede->value; count++; } + if ((client->attributes & NS_CLIENTATTR_HAVEZONEVERSION) != 0) { + INSIST(count < DNS_EDNSOPTIONS); + ednsopts[count].code = DNS_OPT_ZONEVERSION; + ednsopts[count].length = client->zoneversionlength; + ednsopts[count].value = client->zoneversion; + count++; + } if (WANTRC(client)) { dns_name_t *rad = NULL; @@ -1609,8 +1630,7 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) { case DNS_OPT_CLIENT_SUBNET: result = process_ecs(client, &optbuf, optlen); if (result != ISC_R_SUCCESS) { - ns_client_error(client, result); - return result; + goto formerr; } ns_stats_increment( client->manager->sctx->nsstats, @@ -1638,13 +1658,24 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) { result = process_keytag(client, &optbuf, optlen); if (result != ISC_R_SUCCESS) { - ns_client_error(client, result); - return result; + goto formerr; } ns_stats_increment( client->manager->sctx->nsstats, ns_statscounter_keytagopt); break; + case DNS_OPT_ZONEVERSION: + if (optlen != 0 || WANTZONEVERSION(client)) { + result = DNS_R_FORMERR; + goto formerr; + } + ns_stats_increment( + client->manager->sctx->nsstats, + ns_statscounter_zoneversionopt); + client->attributes |= + NS_CLIENTATTR_WANTZONEVERSION; + isc_buffer_forward(&optbuf, optlen); + break; default: ns_stats_increment( client->manager->sctx->nsstats, @@ -1660,6 +1691,17 @@ process_opt(ns_client_t *client, dns_rdataset_t *opt) { client->attributes |= NS_CLIENTATTR_WANTOPT; return result; + +formerr: + if (result == DNS_R_FORMERR || result == DNS_R_OPTERR) { + result = ns_client_addopt(client, client->message, + &client->opt); + if (result == ISC_R_SUCCESS) { + result = DNS_R_FORMERR; + } + } + ns_client_error(client, result); + return result; } static void @@ -1724,6 +1766,7 @@ ns__client_put_cb(void *client0) { */ ns_query_free(client); dns_ede_invalidate(&client->edectx); + client_zoneversion_reset(client); client->magic = 0; diff --git a/lib/ns/include/ns/client.h b/lib/ns/include/ns/client.h index 86d1ff5b6e..773647a2ea 100644 --- a/lib/ns/include/ns/client.h +++ b/lib/ns/include/ns/client.h @@ -224,6 +224,8 @@ struct ns_client { ISC_LINK(ns_client_t) rlink; unsigned char cookie[8]; uint32_t expire; + unsigned char *zoneversion; + uint32_t zoneversionlength; unsigned char *keytag; uint16_t keytag_len; @@ -241,26 +243,28 @@ struct ns_client { #define NS_CLIENT_MAGIC ISC_MAGIC('N', 'S', 'C', 'c') #define NS_CLIENT_VALID(c) ISC_MAGIC_VALID(c, NS_CLIENT_MAGIC) -#define NS_CLIENTATTR_TCP 0x00001 -#define NS_CLIENTATTR_RA 0x00002 /*%< Client gets recursive service */ -#define NS_CLIENTATTR_PKTINFO 0x00004 /*%< pktinfo is valid */ -#define NS_CLIENTATTR_MULTICAST 0x00008 /*%< recv'd from multicast */ -#define NS_CLIENTATTR_WANTDNSSEC 0x00010 /*%< include dnssec records */ -#define NS_CLIENTATTR_WANTNSID 0x00020 /*%< include nameserver ID */ +#define NS_CLIENTATTR_TCP 0x000001 +#define NS_CLIENTATTR_RA 0x000002 /*%< Client gets recursive service */ +#define NS_CLIENTATTR_PKTINFO 0x000004 /*%< pktinfo is valid */ +#define NS_CLIENTATTR_MULTICAST 0x000008 /*%< recv'd from multicast */ +#define NS_CLIENTATTR_WANTDNSSEC 0x000010 /*%< include dnssec records */ +#define NS_CLIENTATTR_WANTNSID 0x000020 /*%< include nameserver ID */ #define NS_CLIENTATTR_BADCOOKIE \ - 0x00040 /*%< Presented cookie is bad/out-of-date */ -#define NS_CLIENTATTR_WANTRC 0x00080 /*%< include Report-Channel */ -#define NS_CLIENTATTR_WANTAD 0x00100 /*%< want AD in response if possible */ -#define NS_CLIENTATTR_WANTCOOKIE 0x00200 /*%< return a COOKIE */ -#define NS_CLIENTATTR_HAVECOOKIE 0x00400 /*%< has a valid COOKIE */ -#define NS_CLIENTATTR_WANTEXPIRE 0x00800 /*%< return seconds to expire */ -#define NS_CLIENTATTR_HAVEEXPIRE 0x01000 /*%< return seconds to expire */ -#define NS_CLIENTATTR_WANTOPT 0x02000 /*%< add opt to reply */ -#define NS_CLIENTATTR_HAVEECS 0x04000 /*%< received an ECS option */ -#define NS_CLIENTATTR_WANTPAD 0x08000 /*%< pad reply */ -#define NS_CLIENTATTR_USEKEEPALIVE 0x10000 /*%< use TCP keepalive */ -#define NS_CLIENTATTR_NOSETFC 0x20000 /*%< don't set servfail cache */ -#define NS_CLIENTATTR_NEEDTCP 0x40000 /*%< send TC=1 */ + 0x000040 /*%< Presented cookie is bad/out-of-date */ +#define NS_CLIENTATTR_WANTRC 0x000080 /*%< include Report-Channel */ +#define NS_CLIENTATTR_WANTAD 0x000100 /*%< want AD in response if possible */ +#define NS_CLIENTATTR_WANTCOOKIE 0x000200 /*%< return a COOKIE */ +#define NS_CLIENTATTR_HAVECOOKIE 0x000400 /*%< has a valid COOKIE */ +#define NS_CLIENTATTR_WANTEXPIRE 0x000800 /*%< return seconds to expire */ +#define NS_CLIENTATTR_HAVEEXPIRE 0x001000 /*%< return seconds to expire */ +#define NS_CLIENTATTR_WANTOPT 0x002000 /*%< add opt to reply */ +#define NS_CLIENTATTR_HAVEECS 0x004000 /*%< received an ECS option */ +#define NS_CLIENTATTR_WANTPAD 0x008000 /*%< pad reply */ +#define NS_CLIENTATTR_USEKEEPALIVE 0x010000 /*%< use TCP keepalive */ +#define NS_CLIENTATTR_NOSETFC 0x020000 /*%< don't set servfail cache */ +#define NS_CLIENTATTR_NEEDTCP 0x040000 /*%< send TC=1 */ +#define NS_CLIENTATTR_WANTZONEVERSION 0x100000 /*%< return zoneversion */ +#define NS_CLIENTATTR_HAVEZONEVERSION 0x200000 /*%< return zoneversion */ /* * Flag to use with the SERVFAIL cache to indicate diff --git a/lib/ns/include/ns/stats.h b/lib/ns/include/ns/stats.h index 5dabdb216f..496e4cd9a5 100644 --- a/lib/ns/include/ns/stats.h +++ b/lib/ns/include/ns/stats.h @@ -84,49 +84,50 @@ enum { ns_statscounter_ecsopt = 46, ns_statscounter_padopt = 47, ns_statscounter_keepaliveopt = 48, + ns_statscounter_zoneversionopt = 49, - ns_statscounter_nxdomainredirect = 49, - ns_statscounter_nxdomainredirect_rlookup = 50, + ns_statscounter_nxdomainredirect = 50, + ns_statscounter_nxdomainredirect_rlookup = 51, - ns_statscounter_cookiein = 51, - ns_statscounter_cookiebadsize = 52, - ns_statscounter_cookiebadtime = 53, - ns_statscounter_cookienomatch = 54, - ns_statscounter_cookiematch = 55, - ns_statscounter_cookienew = 56, - ns_statscounter_badcookie = 57, + ns_statscounter_cookiein = 52, + ns_statscounter_cookiebadsize = 53, + ns_statscounter_cookiebadtime = 54, + ns_statscounter_cookienomatch = 55, + ns_statscounter_cookiematch = 56, + ns_statscounter_cookienew = 57, + ns_statscounter_badcookie = 58, - ns_statscounter_nxdomainsynth = 58, - ns_statscounter_nodatasynth = 59, - ns_statscounter_wildcardsynth = 60, + ns_statscounter_nxdomainsynth = 59, + ns_statscounter_nodatasynth = 60, + ns_statscounter_wildcardsynth = 61, - ns_statscounter_trystale = 61, - ns_statscounter_usedstale = 62, + ns_statscounter_trystale = 62, + ns_statscounter_usedstale = 63, - ns_statscounter_prefetch = 63, - ns_statscounter_keytagopt = 64, + ns_statscounter_prefetch = 64, + ns_statscounter_keytagopt = 65, - ns_statscounter_tcphighwater = 65, + ns_statscounter_tcphighwater = 66, - ns_statscounter_reclimitdropped = 66, + ns_statscounter_reclimitdropped = 67, - ns_statscounter_updatequota = 67, + ns_statscounter_updatequota = 68, - ns_statscounter_recurshighwater = 68, + ns_statscounter_recurshighwater = 69, - ns_statscounter_dot = 69, - ns_statscounter_doh = 70, - ns_statscounter_dohplain = 71, + ns_statscounter_dot = 70, + ns_statscounter_doh = 71, + ns_statscounter_dohplain = 72, - ns_statscounter_proxyudp = 72, - ns_statscounter_proxytcp = 73, - ns_statscounter_proxydot = 74, - ns_statscounter_proxydoh = 75, - ns_statscounter_proxydohplain = 76, - ns_statscounter_encryptedproxydot = 77, - ns_statscounter_encryptedproxydoh = 78, + ns_statscounter_proxyudp = 73, + ns_statscounter_proxytcp = 74, + ns_statscounter_proxydot = 75, + ns_statscounter_proxydoh = 76, + ns_statscounter_proxydohplain = 77, + ns_statscounter_encryptedproxydot = 78, + ns_statscounter_encryptedproxydoh = 79, - ns_statscounter_max = 79, + ns_statscounter_max = 80, }; void diff --git a/lib/ns/query.c b/lib/ns/query.c index c5e07bfecd..218900dcaf 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -7938,6 +7938,55 @@ query_getexpire(query_ctx_t *qctx) { } } +/* + * Set the zone version, if requested, when answering from a secondary, + * mirror, or primary zone. + */ +static void +query_getzoneversion(query_ctx_t *qctx) { + CCTRACE(ISC_LOG_DEBUG(3), __func__); + + if (qctx->zone == NULL || !qctx->is_zone || + qctx->client->query.restarts != 0 || + (qctx->client->attributes & NS_CLIENTATTR_WANTZONEVERSION) == 0 || + (qctx->client->attributes & NS_CLIENTATTR_HAVEZONEVERSION) != 0) + { + return; + } + + switch (dns_zone_gettype(qctx->zone)) { + case dns_zone_mirror: + case dns_zone_primary: + case dns_zone_secondary: { + isc_buffer_t b; + unsigned char buf[128]; + isc_buffer_init(&b, buf, sizeof(buf)); + isc_result_t result = dns_zone_getzoneversion(qctx->zone, &b); + if (result == ISC_R_SUCCESS) { + size_t len = isc_buffer_usedlength(&b); + /* + * Sanity check zone version from database + * implementations. Minimum length and type 0 + * contraints. + */ + if (len < 2 || (buf[1] == 0 && len != 6)) { + return; + } + qctx->client->attributes |= + NS_CLIENTATTR_HAVEZONEVERSION; + INSIST(qctx->client->zoneversion == NULL); + qctx->client->zoneversion = + isc_mem_get(qctx->client->manager->mctx, len); + qctx->client->zoneversionlength = len; + memmove(qctx->client->zoneversion, buf, len); + } + break; + } + default: + break; + } +} + /*% * Fill the ANSWER section of a positive response. */ @@ -8101,6 +8150,11 @@ query_respond(query_ctx_t *qctx) { */ query_getexpire(qctx); + /* + * Set the zone version + */ + query_getzoneversion(qctx); + result = query_addanswer(qctx); if (result != ISC_R_COMPLETE) { return result;