new: usr: Add support for EDNS ZONEVERSION option

`dig` and `named` can now make requests with an EDNS `ZONEVERSION` option present.

Two new `named.conf` options have been added: `request-zoneversion` and
`provide-zoneversion`.  `request-zoneversion` is `off` by default. `provide-zoneversion`
is `on` by default.

Closes #4767

Merge branch '4767-implement-zoneversion' into 'main'

See merge request isc-projects/bind9!9103
This commit is contained in:
Mark Andrews 2025-03-24 23:09:39 +00:00
commit 908840157e
41 changed files with 865 additions and 148 deletions

View file

@ -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:

View file

@ -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
~~~~~~~~~~~~~~~~

View file

@ -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,

View file

@ -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;

View file

@ -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 <boolean>;\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\

View file

@ -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);

View file

@ -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");

View file

@ -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);

View file

@ -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;

View file

@ -36,4 +36,5 @@ zone "example" {
zone "example.tld" {
type primary;
file "example.tld.db";
provide-zoneversion no;
};

View file

@ -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 <yamlget.out.test$n
expected="{'ZONE': 'example', 'SOA-SERIAL': 2000042407}"
[ "$value" = "$expected" ] || ret=1
if [ $ret -ne 0 ]; then echo_i "failed"; fi
status=$((status + ret))
fi
n=$((n + 1))
echo_i "checking dig +ednsopt=ZONEVERSION:<answer> 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

View file

@ -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"));

View file

@ -31,6 +31,7 @@ options {
resolver-query-timeout 5000; # 5 seconds
attach-cache "globalcache";
max-recursion-queries 100;
request-zoneversion yes;
};
trust-anchors { };

View file

@ -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";

View file

@ -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

View file

@ -127,3 +127,7 @@
``zoneload``
Loading of zones and creation of automatic empty zones.
``zoneversion``
ZONEVERSION options received from upstream servers.

View file

@ -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`

View file

@ -33,6 +33,7 @@ zone <string> [ <class> ] {
notify-source ( <ipv4_address> | * );
notify-source-v6 ( <ipv6_address> | * );
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
provide-zoneversion <boolean>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;

View file

@ -226,6 +226,7 @@ options {
preferred-glue <string>;
prefetch <integer> [ <integer> ];
provide-ixfr <boolean>;
provide-zoneversion <boolean>;
qname-minimization ( strict | relaxed | disabled | off );
query-source [ address ] ( <ipv4_address> | * | none );
query-source-v6 [ address ] ( <ipv6_address> | * | none );
@ -254,6 +255,7 @@ options {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
resolver-use-dns64 <boolean>;
@ -343,6 +345,7 @@ server <netprefix> {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;
tcp-keepalive <boolean>;
@ -509,6 +512,7 @@ view <string> [ <class> ] {
preferred-glue <string>;
prefetch <integer> [ <integer> ];
provide-ixfr <boolean>;
provide-zoneversion <boolean>;
qname-minimization ( strict | relaxed | disabled | off );
query-source [ address ] ( <ipv4_address> | * | none );
query-source-v6 [ address ] ( <ipv6_address> | * | none );
@ -534,6 +538,7 @@ view <string> [ <class> ] {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
resolver-use-dns64 <boolean>;
@ -561,6 +566,7 @@ view <string> [ <class> ] {
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
request-zoneversion <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;
tcp-keepalive <boolean>;

View file

@ -51,6 +51,7 @@ zone <string> [ <class> ] {
parental-agents [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
parental-source ( <ipv4_address> | * );
parental-source-v6 ( <ipv6_address> | * );
provide-zoneversion <boolean>;
send-report-channel <string>;
serial-update-method ( date | increment | unixtime );
sig-signing-nodes <integer>;

View file

@ -50,6 +50,7 @@ zone <string> [ <class> ] {
parental-source ( <ipv4_address> | * );
parental-source-v6 ( <ipv6_address> | * );
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <server-list> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
provide-zoneversion <boolean>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;

View file

@ -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;
}

View file

@ -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
*/

View file

@ -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)

View file

@ -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);

View file

@ -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,

View file

@ -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;

View file

@ -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__)

View file

@ -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;
}

View file

@ -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)

View file

@ -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;

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>
@ -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);
}
}
/*

View file

@ -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.
*/

View file

@ -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 */

View file

@ -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 */

View file

@ -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 },

View file

@ -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 },

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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;