new: usr: implement the 'request-ixfr-max-diffs' configuration option

The new 'request-ixfr-max-diffs' configuration option sets the
maximum number of incoming incremental zone transfer (IXFR) differences,
exceeding which triggers a full zone transfer (AXFR).

Closes #4389

Merge branch '4389-request-ixfr-max-diffs' into 'main'

Closes #4389

See merge request isc-projects/bind9!9094
This commit is contained in:
Arаm Sаrgsyаn 2024-08-22 15:33:17 +00:00
commit 99b18bab7e
21 changed files with 239 additions and 25 deletions

View file

@ -190,6 +190,7 @@ options {\n\
recursion true;\n\
request-expire true;\n\
request-ixfr true;\n\
request-ixfr-max-diffs 0;\n\
require-server-cookie no;\n\
root-key-sentinel yes;\n\
servfail-ttl 1;\n\

View file

@ -1469,6 +1469,13 @@ configure_peer(const cfg_obj_t *cpeer, isc_mem_t *mctx, dns_peer_t **peerp) {
CHECK(dns_peer_setrequestixfr(peer, cfg_obj_asboolean(obj)));
}
obj = NULL;
(void)cfg_map_get(cpeer, "request-ixfr-max-diffs", &obj);
if (obj != NULL) {
CHECK(dns_peer_setrequestixfrmaxdiffs(peer,
cfg_obj_asuint32(obj)));
}
obj = NULL;
(void)cfg_map_get(cpeer, "request-nsid", &obj);
if (obj != NULL) {

View file

@ -1418,6 +1418,11 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
INSIST(result == ISC_R_SUCCESS);
dns_zone_setrequestixfr(zone, cfg_obj_asboolean(obj));
obj = NULL;
result = named_config_get(maps, "request-ixfr-max-diffs", &obj);
INSIST(result == ISC_R_SUCCESS);
dns_zone_setrequestixfrmaxdiffs(zone, cfg_obj_asuint32(obj));
obj = NULL;
checknames(ztype, maps, &obj);
INSIST(obj != NULL);

View file

@ -0,0 +1,22 @@
/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
/*
* request-ixfr clause is not allowed in zone of type primary.
*/
zone dummy {
type primary;
request-ixfr-max-diffs 100;
file "xxxx";
};

View file

@ -29,6 +29,7 @@ server 0.0.0.0 {
query-source 0.0.0.0;
request-expire no;
request-ixfr no;
request-ixfr-max-diffs 0;
request-nsid no;
require-cookie no;
send-cookie no;
@ -52,6 +53,7 @@ server :: {
query-source-v6 ::;
request-expire no;
request-ixfr no;
request-ixfr-max-diffs 0;
request-nsid no;
require-cookie no;
send-cookie no;

View file

@ -0,0 +1,18 @@
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
;
; SPDX-License-Identifier: MPL-2.0
;
; This Source Code Form is subject to the terms of the Mozilla Public
; License, v. 2.0. If a copy of the MPL was not distributed with this
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
;
; See the COPYRIGHT file distributed with this work for additional
; information regarding copyright ownership.
$TTL 3600
@ IN SOA . . 0 0 0 0 0
@ IN NS ns1
@ IN NS ns6
ns1 IN A 10.53.0.1
ns6 IN A 10.53.0.6
$GENERATE 1-25 host$ A 1.2.3.$

View file

@ -74,6 +74,12 @@ zone "ixfr-too-big" {
file "ixfr-too-big.db";
};
zone "ixfr-too-many-diffs" {
type primary;
allow-update { any; };
file "ixfr-too-many-diffs.db";
};
zone "xfer-stats" {
type primary;
file "xfer-stats.db";

View file

@ -90,3 +90,10 @@ zone "ixfr-too-big" {
primaries { 10.53.0.1; };
file "ixfr-too-big.bk";
};
zone "ixfr-too-many-diffs" {
type secondary;
request-ixfr-max-diffs 3;
primaries { 10.53.0.1; };
file "ixfr-too-many-diffs.bk";
};

View file

@ -591,6 +591,27 @@ wait_for_log 10 "$msg" ns6/named.run || tmp=1
if test $tmp != 0; then echo_i "failed"; fi
status=$((status + tmp))
n=$((n + 1))
echo_i "test that a zone with too many diffs (IXFR) is retried with AXFR ($n)"
tmp=0
nextpart ns6/named.run >/dev/null
$NSUPDATE <<EOF
zone ixfr-too-many-diffs
server 10.53.0.1 ${PORT}
update add the-31st-record.ixfr-too-many-diffs 0 TXT too
update add the-32nd-record.ixfr-too-many-diffs 0 TXT many
update add the-33rd-record.ixfr-too-many-diffs 0 TXT diffs
update add the-34th-record.ixfr-too-many-diffs 0 TXT for
update add the-35th-record.ixfr-too-many-diffs 0 TXT ixfr
send
EOF
msg="'ixfr-too-many-diffs/IN' from 10.53.0.1#${PORT}: Transfer status: success"
wait_for_log 10 "$msg" ns6/named.run || tmp=1
msg="'ixfr-too-many-diffs/IN' from 10.53.0.1#${PORT}: too many diffs, retrying with AXFR"
grep -F "$msg" ns6/named.run >/dev/null || tmp=1
if test $tmp != 0; then echo_i "failed"; fi
status=$((status + tmp))
n=$((n + 1))
echo_i "checking whether dig calculates AXFR statistics correctly ($n)"
tmp=0

View file

@ -2503,6 +2503,20 @@ Boolean Options
or view setting for that zone. It may also be set in the
:namedconf:ref:`server` block.
.. namedconf:statement:: request-ixfr-max-diffs
:tags: transfer
:short: Sets the maximum number of incoming incremental zone transfer (IXFR) differences, exceeding which triggers a full zone transfer (AXFR).
The :any:`request-ixfr-max-diffs` clause configured for the local server,
acting as a secondary, sets the maximum number of incremental zone transfer
(IXFR) differences after which the secondary server will abort the
incremental zone transfer process and requeast a full zone transfer (AXFR).
The default value is ``0``, which means there is no maximum limit.
It may also be set in the zone block; if set there, it overrides the global
or view setting for that zone. It may also be set in the
:namedconf:ref:`server` block.
.. namedconf:statement:: request-expire
:tags: transfer, query
:short: Specifies whether the local server requests the EDNS EXPIRE value, when acting as a secondary.

View file

@ -34,6 +34,7 @@ zone <string> [ <class> ] {
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
transfer-source ( <ipv4_address> | * );
transfer-source-v6 ( <ipv6_address> | * );
try-tcp-refresh <boolean>;

View file

@ -257,6 +257,7 @@ options {
recursive-clients <integer>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
@ -346,6 +347,7 @@ server <netprefix> {
query-source-v6 [ address ] ( <ipv6_address> | * );
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;
@ -539,6 +541,7 @@ view <string> [ <class> ] {
recursion <boolean>;
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
require-server-cookie <boolean>;
resolver-query-timeout <integer>;
@ -564,6 +567,7 @@ view <string> [ <class> ] {
query-source-v6 [ address ] ( <ipv6_address> | * );
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
request-nsid <boolean>;
require-cookie <boolean>;
send-cookie <boolean>;

View file

@ -51,6 +51,7 @@ zone <string> [ <class> ] {
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
request-expire <boolean>;
request-ixfr <boolean>;
request-ixfr-max-diffs <integer>;
sig-signing-nodes <integer>;
sig-signing-signatures <integer>;
sig-signing-type <integer>;

View file

@ -100,6 +100,12 @@ dns_peer_setrequestixfr(dns_peer_t *peer, bool newval);
isc_result_t
dns_peer_getrequestixfr(dns_peer_t *peer, bool *retval);
isc_result_t
dns_peer_setrequestixfrmaxdiffs(dns_peer_t *peer, uint32_t newval);
isc_result_t
dns_peer_getrequestixfrmaxdiffs(dns_peer_t *peer, uint32_t *retval);
isc_result_t
dns_peer_setprovideixfr(dns_peer_t *peer, bool newval);

View file

@ -53,7 +53,7 @@ ISC_LANG_BEGINDECLS
isc_result_t
dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
const isc_sockaddr_t *primaryaddr,
uint32_t ixfr_maxdiffs, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,

View file

@ -2442,6 +2442,25 @@ dns_zone_setrequestixfr(dns_zone_t *zone, bool flag);
* \li 'zone' to be valid.
*/
bool
dns_zone_getrequestixfrmaxdiffs(dns_zone_t *zone);
/*%
* Returns the value of the request-ixfr-max-diffs option in the zone.
*
* Requires:
* \li 'zone' to be valid.
*/
void
dns_zone_setrequestixfrmaxdiffs(dns_zone_t *zone, uint32_t maxmsgs);
/*%
* Sets the request-ixfr-max-diffs option for the zone. 0 means unlimited. The
* default value is determined by the setting of this option in the view.
*
* Requires:
* \li 'zone' to be valid.
*/
uint32_t
dns_zone_getixfrratio(dns_zone_t *zone);
/*%

View file

@ -14,6 +14,7 @@
/*! \file */
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <isc/mem.h>
@ -50,6 +51,7 @@ struct dns_peer {
bool bogus;
dns_transfer_format_t transfer_format;
uint32_t transfers;
uint32_t request_ixfr_maxdiffs;
bool support_ixfr;
bool provide_ixfr;
bool request_ixfr;
@ -78,22 +80,29 @@ struct dns_peer {
/*%
* Bit positions in the dns_peer_t structure flags field
*/
#define BOGUS_BIT 0
#define SERVER_TRANSFER_FORMAT_BIT 1
#define TRANSFERS_BIT 2
#define PROVIDE_IXFR_BIT 3
#define REQUEST_IXFR_BIT 4
#define SUPPORT_EDNS_BIT 5
#define SERVER_UDPSIZE_BIT 6
#define SERVER_MAXUDP_BIT 7
#define REQUEST_NSID_BIT 8
#define SEND_COOKIE_BIT 9
#define REQUEST_EXPIRE_BIT 10
#define EDNS_VERSION_BIT 11
#define FORCE_TCP_BIT 12
#define SERVER_PADDING_BIT 13
#define REQUEST_TCP_KEEPALIVE_BIT 14
#define REQUIRE_COOKIE_BIT 15
enum {
BOGUS_BIT = 0,
SERVER_TRANSFER_FORMAT_BIT,
TRANSFERS_BIT,
PROVIDE_IXFR_BIT,
REQUEST_IXFR_BIT,
REQUEST_IXFRMAXDIFFS_BIT,
SUPPORT_EDNS_BIT,
SERVER_UDPSIZE_BIT,
SERVER_MAXUDP_BIT,
REQUEST_NSID_BIT,
SEND_COOKIE_BIT,
REQUEST_EXPIRE_BIT,
EDNS_VERSION_BIT,
FORCE_TCP_BIT,
SERVER_PADDING_BIT,
REQUEST_TCP_KEEPALIVE_BIT,
REQUIRE_COOKIE_BIT,
DNS_PEER_FLAGS_COUNT
};
STATIC_ASSERT(DNS_PEER_FLAGS_COUNT <= CHAR_BIT * sizeof(uint32_t),
"dns_peer_t structure flags fields are too many for uint32_t");
static void
peerlist_delete(dns_peerlist_t **list);
@ -372,6 +381,8 @@ ACCESS_OPTION(maxudp, SERVER_MAXUDP_BIT, uint16_t, maxudp)
ACCESS_OPTION(provideixfr, PROVIDE_IXFR_BIT, bool, provide_ixfr)
ACCESS_OPTION(requestexpire, REQUEST_EXPIRE_BIT, bool, request_expire)
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(requirecookie, REQUIRE_COOKIE_BIT, bool, require_cookie)
ACCESS_OPTION(sendcookie, SEND_COOKIE_BIT, bool, send_cookie)

View file

@ -178,6 +178,8 @@ struct dns_xfrin {
dns_rdatacallbacks_t axfr;
struct {
uint32_t diffs;
uint32_t maxdiffs;
uint32_t request_serial;
uint32_t current_serial;
dns_journal_t *journal;
@ -212,7 +214,8 @@ typedef struct xfrin_work {
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_loop_t *loop,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
dns_rdatatype_t reqtype, uint32_t ixfr_maxdiffs,
const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
@ -459,6 +462,7 @@ ixfr_putdata(dns_xfrin_t *xfr, dns_diffop_t op, dns_name_t *name, dns_ttl_t ttl,
dns_difftuple_create(xfr->diff.mctx, op, name, ttl, rdata, &tuple);
dns_diff_append(&xfr->diff, &tuple);
xfr->ixfr.diffs++;
failure:
return (result);
}
@ -863,7 +867,7 @@ failure:
isc_result_t
dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
const isc_sockaddr_t *primaryaddr,
uint32_t ixfr_maxdiffs, const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
@ -889,7 +893,7 @@ dns_xfrin_create(dns_zone_t *zone, dns_rdatatype_t xfrtype,
}
xfrin_create(mctx, zone, db, loop, zonename, dns_zone_getclass(zone),
xfrtype, primaryaddr, sourceaddr, tsigkey,
xfrtype, ixfr_maxdiffs, primaryaddr, sourceaddr, tsigkey,
soa_transport_type, transport, tlsctx_cache, &xfr);
if (db != NULL) {
@ -1096,6 +1100,8 @@ xfrin_reset(dns_xfrin_t *xfr) {
dns_diff_clear(&xfr->diff);
xfr->ixfr.diffs = 0;
if (xfr->ixfr.journal != NULL) {
dns_journal_destroy(&xfr->ixfr.journal);
}
@ -1145,7 +1151,8 @@ xfrin_fail(dns_xfrin_t *xfr, isc_result_t result, const char *msg) {
static void
xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_loop_t *loop,
dns_name_t *zonename, dns_rdataclass_t rdclass,
dns_rdatatype_t reqtype, const isc_sockaddr_t *primaryaddr,
dns_rdatatype_t reqtype, uint32_t ixfr_maxdiffs,
const isc_sockaddr_t *primaryaddr,
const isc_sockaddr_t *sourceaddr, dns_tsigkey_t *tsigkey,
dns_transport_type_t soa_transport_type,
dns_transport_t *transport, isc_tlsctx_cache_t *tlsctx_cache,
@ -1157,6 +1164,7 @@ xfrin_create(isc_mem_t *mctx, dns_zone_t *zone, dns_db_t *db, isc_loop_t *loop,
.shutdown_result = ISC_R_UNSET,
.rdclass = rdclass,
.reqtype = reqtype,
.ixfr.maxdiffs = ixfr_maxdiffs,
.maxrecords = dns_zone_getmaxrecords(zone),
.primaryaddr = *primaryaddr,
.sourceaddr = *sourceaddr,
@ -1902,6 +1910,19 @@ xfrin_recv_done(isc_result_t result, isc_region_t *region, void *arg) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdataset_current(rds, &rdata);
CHECK(xfr_rr(xfr, name, rds->ttl, &rdata));
/*
* Did we hit the maximum ixfr diffs limit?
*/
if (xfr->reqtype == dns_rdatatype_ixfr &&
xfr->ixfr.maxdiffs != 0 &&
xfr->ixfr.diffs >= xfr->ixfr.maxdiffs)
{
xfrin_log(xfr, ISC_LOG_DEBUG(3),
"too many diffs, "
"retrying with AXFR");
goto try_axfr;
}
}
}
}

View file

@ -465,6 +465,7 @@ struct dns_zone {
* whether ixfr is requested
*/
bool requestixfr;
uint32_t requestixfr_maxdiffs;
uint32_t ixfr_ratio;
/*%
@ -18260,6 +18261,7 @@ got_transfer_quota(void *arg) {
char primary[ISC_SOCKADDR_FORMATSIZE];
char source[ISC_SOCKADDR_FORMATSIZE];
dns_rdatatype_t xfrtype;
uint32_t ixfr_maxdiffs = 0;
isc_netaddr_t primaryip;
isc_sockaddr_t primaryaddr;
isc_sockaddr_t sourceaddr;
@ -18326,12 +18328,20 @@ got_transfer_quota(void *arg) {
UNLOCK_ZONE(zone);
} else {
bool use_ixfr = true;
if (peer != NULL) {
result = dns_peer_getrequestixfr(peer, &use_ixfr);
}
if (peer == NULL || result != ISC_R_SUCCESS) {
use_ixfr = zone->requestixfr;
}
if (peer != NULL) {
result = dns_peer_getrequestixfrmaxdiffs(
peer, &ixfr_maxdiffs);
}
if (peer == NULL || result != ISC_R_SUCCESS) {
ixfr_maxdiffs = zone->requestixfr_maxdiffs;
}
if (!use_ixfr) {
dns_zone_logc(zone, DNS_LOGCATEGORY_XFER_IN,
ISC_LOG_DEBUG(1),
@ -18415,10 +18425,10 @@ got_transfer_quota(void *arg) {
zmgr_tlsctx_attach(zone->zmgr, &zmgr_tlsctx_cache);
result = dns_xfrin_create(zone, xfrtype, &primaryaddr, &sourceaddr,
zone->tsigkey, soa_transport_type,
zone->transport, zmgr_tlsctx_cache,
zone->mctx, zone_xfrdone, &zone->xfr);
result = dns_xfrin_create(
zone, xfrtype, ixfr_maxdiffs, &primaryaddr, &sourceaddr,
zone->tsigkey, soa_transport_type, zone->transport,
zmgr_tlsctx_cache, zone->mctx, zone_xfrdone, &zone->xfr);
isc_tlsctx_cache_detach(&zmgr_tlsctx_cache);
@ -23139,6 +23149,18 @@ dns_zone_getrequestixfr(dns_zone_t *zone) {
return (zone->requestixfr);
}
void
dns_zone_setrequestixfrmaxdiffs(dns_zone_t *zone, uint32_t maxdiffs) {
REQUIRE(DNS_ZONE_VALID(zone));
zone->requestixfr_maxdiffs = maxdiffs;
}
bool
dns_zone_getrequestixfrmaxdiffs(dns_zone_t *zone) {
REQUIRE(DNS_ZONE_VALID(zone));
return (zone->requestixfr_maxdiffs);
}
void
dns_zone_setixfrratio(dns_zone_t *zone, uint32_t ratio) {
REQUIRE(DNS_ZONE_VALID(zone));

View file

@ -4344,6 +4344,13 @@ static struct {
{ "tcp-only", dns_peer_setforcetcp },
};
static struct {
const char *name;
isc_result_t (*set)(dns_peer_t *peer, uint32_t newval);
} uint32s[] = {
{ "request-ixfr-max-diffs", dns_peer_setrequestixfrmaxdiffs },
};
static isc_result_t
check_servers(const cfg_obj_t *config, const cfg_obj_t *voptions,
isc_symtab_t *symtab, isc_mem_t *mctx) {
@ -4502,6 +4509,22 @@ check_servers(const cfg_obj_t *config, const cfg_obj_t *voptions,
}
}
}
for (i = 0; i < ARRAY_SIZE(uint32s); i++) {
const cfg_obj_t *opt = NULL;
cfg_map_get(v1, uint32s[i].name, &opt);
if (opt != NULL) {
tresult = (uint32s[i].set)(
peer, cfg_obj_asuint32(opt));
if (tresult != ISC_R_SUCCESS) {
cfg_obj_log(opt, ISC_LOG_ERROR,
"setting server option "
"'%s' failed: %s",
uint32s[i].name,
isc_result_totext(tresult));
result = ISC_R_FAILURE;
}
}
}
dns_peer_detach(&peer);
}
return (result);

View file

@ -2484,6 +2484,8 @@ static cfg_clausedef_t zone_clauses[] = {
CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
{ "request-ixfr", &cfg_type_boolean,
CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
{ "request-ixfr-max-diffs", &cfg_type_uint32,
CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR },
{ "serial-update-method", &cfg_type_updatemethod, CFG_ZONE_PRIMARY },
{ "sig-signing-nodes", &cfg_type_uint32,
CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY },
@ -2721,6 +2723,7 @@ static cfg_clausedef_t server_clauses[] = {
{ "query-source-v6", &cfg_type_querysource6, 0 },
{ "request-expire", &cfg_type_boolean, 0 },
{ "request-ixfr", &cfg_type_boolean, 0 },
{ "request-ixfr-max-diffs", &cfg_type_uint32, 0 },
{ "request-nsid", &cfg_type_boolean, 0 },
{ "request-sit", NULL, CFG_CLAUSEFLAG_ANCIENT },
{ "require-cookie", &cfg_type_boolean, 0 },