2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-29 13:38:26 +00:00

Merge branch '3405-security-limit-the-number-of-resource-records-in-rrset' into 'v9.20.0-release'

Limit the number of RRs in RRSets

See merge request isc-private/bind9!694
This commit is contained in:
Nicki Křížek 2024-06-10 15:01:48 +00:00
commit 24e8cc7b38
54 changed files with 4002 additions and 53 deletions

16
CHANGES
View File

@ -1,3 +1,19 @@
6401. [security] An excessively large number of rrtypes per owner can
slow down database query processing, so a limit has been
placed on the number of rrtypes that can be stored per
owner (node) in a cache or zone database. This is
configured with the new "max-rrtypes-per-name" option,
and defaults to 100. (CVE-2024-1737)
[GL #3403] [GL #4548]
6400. [security] Excessively large rdatasets can slow down database
query processing, so a limit has been placed on the
number of records that can be stored per rdataset
in a cache or zone database. This is configured
with the new "max-records-per-type" option, and
defaults to 100. (CVE-2024-1737)
[GL #497] [GL #3405]
6399. [security] Malicious DNS client that sends many queries over 6399. [security] Malicious DNS client that sends many queries over
TCP but never reads responses can cause server to TCP but never reads responses can cause server to
respond slowly or not respond at all for other respond slowly or not respond at all for other

View File

@ -222,8 +222,10 @@ options {\n\
ixfr-from-differences false;\n\ ixfr-from-differences false;\n\
max-journal-size default;\n\ max-journal-size default;\n\
max-records 0;\n\ max-records 0;\n\
max-records-per-type 100;\n\
max-refresh-time 2419200; /* 4 weeks */\n\ max-refresh-time 2419200; /* 4 weeks */\n\
max-retry-time 1209600; /* 2 weeks */\n\ max-retry-time 1209600; /* 2 weeks */\n\
max-types-per-name 100;\n\
max-transfer-idle-in 60;\n\ max-transfer-idle-in 60;\n\
max-transfer-idle-out 60;\n\ max-transfer-idle-out 60;\n\
max-transfer-time-in 120;\n\ max-transfer-time-in 120;\n\

View File

@ -5454,6 +5454,24 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj), dns_resolver_setclientsperquery(view->resolver, cfg_obj_asuint32(obj),
max_clients_per_query); max_clients_per_query);
/*
* This is used for the cache and also as a default value
* for zone databases.
*/
obj = NULL;
result = named_config_get(maps, "max-records-per-type", &obj);
INSIST(result == ISC_R_SUCCESS);
dns_view_setmaxrrperset(view, cfg_obj_asuint32(obj));
/*
* This is used for the cache and also as a default value
* for zone databases.
*/
obj = NULL;
result = named_config_get(maps, "max-types-per-name", &obj);
INSIST(result == ISC_R_SUCCESS);
dns_view_setmaxtypepername(view, cfg_obj_asuint32(obj));
obj = NULL; obj = NULL;
result = named_config_get(maps, "max-recursion-depth", &obj); result = named_config_get(maps, "max-recursion-depth", &obj);
INSIST(result == ISC_R_SUCCESS); INSIST(result == ISC_R_SUCCESS);

View File

@ -1074,6 +1074,22 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig,
dns_zone_setmaxrecords(zone, 0); dns_zone_setmaxrecords(zone, 0);
} }
obj = NULL;
result = named_config_get(maps, "max-records-per-type", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
dns_zone_setmaxrrperset(mayberaw, cfg_obj_asuint32(obj));
if (zone != mayberaw) {
dns_zone_setmaxrrperset(zone, 0);
}
obj = NULL;
result = named_config_get(maps, "max-types-per-name", &obj);
INSIST(result == ISC_R_SUCCESS && obj != NULL);
dns_zone_setmaxtypepername(mayberaw, cfg_obj_asuint32(obj));
if (zone != mayberaw) {
dns_zone_setmaxtypepername(zone, 0);
}
if (raw != NULL && filename != NULL) { if (raw != NULL && filename != NULL) {
#define SIGNED ".signed" #define SIGNED ".signed"
size_t signedlen = strlen(filename) + sizeof(SIGNED); size_t signedlen = strlen(filename) + sizeof(SIGNED);

View File

@ -98,6 +98,7 @@ options {
tcp-initial-timeout 1200; tcp-initial-timeout 1200;
transfers-in 100; transfers-in 100;
transfers-out 100; transfers-out 100;
max-records-per-type 0;
}; };
trust-anchors { }; trust-anchors { };

View File

@ -52,6 +52,7 @@ options {
ixfr-from-differences yes; ixfr-from-differences yes;
check-integrity no; check-integrity no;
dnssec-validation yes; dnssec-validation yes;
max-records-per-type 0;
transfers-in 100; transfers-in 100;
transfers-out 100; transfers-out 100;
}; };

View File

@ -44,6 +44,7 @@ options {
ixfr-from-differences yes; ixfr-from-differences yes;
check-integrity no; check-integrity no;
dnssec-validation yes; dnssec-validation yes;
max-records-per-type 0;
}; };
trust-anchors { }; trust-anchors { };

View File

@ -52,6 +52,7 @@ options {
ixfr-from-differences yes; ixfr-from-differences yes;
check-integrity no; check-integrity no;
dnssec-validation yes; dnssec-validation yes;
max-records-per-type 0;
}; };
trust-anchors { }; trust-anchors { };

View File

@ -40,6 +40,7 @@ options {
ixfr-from-differences yes; ixfr-from-differences yes;
check-integrity no; check-integrity no;
dnssec-validation yes; dnssec-validation yes;
max-records-per-type 0;
}; };
trust-anchors { }; trust-anchors { };

View File

@ -23,6 +23,7 @@ options {
notify yes; notify yes;
minimal-responses no; minimal-responses no;
dnssec-validation no; dnssec-validation no;
max-records-per-type 0;
}; };
zone "." { zone "." {

View File

@ -26,7 +26,12 @@ $CHECKZONE -D -F raw=0 -o example.db.compat example-compat \
example.db >/dev/null 2>&1 example.db >/dev/null 2>&1
$CHECKZONE -D -F raw -L 3333 -o example.db.serial.raw example \ $CHECKZONE -D -F raw -L 3333 -o example.db.serial.raw example \
example.db >/dev/null 2>&1 example.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o large.db.raw large large.db >/dev/null 2>&1 $CHECKZONE -D -F raw -o under-limit.db.raw under-limit under-limit.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o under-limit-kasp.db.raw under-limit-kasp under-limit-kasp.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o on-limit.db.raw on-limit on-limit.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o on-limit-kasp.db.raw on-limit-kasp on-limit-kasp.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o over-limit.db.raw over-limit over-limit.db >/dev/null 2>&1
$CHECKZONE -D -F raw -o 255types.db.raw 255types 255types.db >/dev/null 2>&1
$KEYGEN -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK signed >/dev/null 2>&1 $KEYGEN -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" -f KSK signed >/dev/null 2>&1
$KEYGEN -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" signed >/dev/null 2>&1 $KEYGEN -q -a "$DEFAULT_ALGORITHM" -b "$DEFAULT_BITS" signed >/dev/null 2>&1

View File

@ -23,6 +23,8 @@ options {
session-keyfile "session.key"; session-keyfile "session.key";
servfail-ttl 0; servfail-ttl 0;
dnssec-validation no; dnssec-validation no;
max-records-per-type 2050;
max-types-per-name 500;
}; };
key rndc_key { key rndc_key {
@ -78,9 +80,48 @@ zone "transfer4" {
}; };
zone "large" { zone "under-limit" {
type primary; type primary;
file "large.db.raw"; file "under-limit.db.raw";
masterfile-format raw;
allow-transfer { any; };
};
zone "under-limit-kasp" {
type primary;
file "under-limit-kasp.db.raw";
masterfile-format raw;
dnssec-policy masterformat;
allow-transfer { any; };
};
zone "on-limit" {
type primary;
file "on-limit.db.raw";
masterfile-format raw;
allow-transfer { any; };
};
zone "on-limit-kasp" {
type primary;
file "on-limit-kasp.db.raw";
masterfile-format raw;
dnssec-policy masterformat;
inline-signing no;
allow-update { any; };
allow-transfer { any; };
};
zone "over-limit" {
type primary;
file "over-limit.db.raw";
masterfile-format raw;
allow-transfer { any; };
};
zone "255types" {
type primary;
file "255types.db.raw";
masterfile-format raw; masterfile-format raw;
allow-transfer { any; }; allow-transfer { any; };
}; };

View File

@ -22,6 +22,8 @@ options {
notify no; notify no;
servfail-ttl 0; servfail-ttl 0;
dnssec-validation no; dnssec-validation no;
max-records-per-type 2000;
max-types-per-name 200;
}; };
zone "example" { zone "example" {
@ -56,9 +58,37 @@ zone "transfer4" {
file "transfer.db.full"; file "transfer.db.full";
}; };
zone "large" { zone "under-limit" {
type secondary; type secondary;
primaries { 10.53.0.1; }; primaries { 10.53.0.1; };
masterfile-format raw; masterfile-format raw;
file "large.bk"; file "under-limit.bk";
};
zone "under-limit-kasp" {
type secondary;
primaries { 10.53.0.1; };
masterfile-format raw;
file "under-limit-kasp.bk";
};
zone "on-limit" {
type secondary;
primaries { 10.53.0.1; };
masterfile-format raw;
file "on-limit.bk";
};
zone "on-limit-kasp" {
type secondary;
primaries { 10.53.0.1; };
masterfile-format raw;
file "on-limit-kasp.bk";
};
zone "255types" {
type secondary;
primaries { 10.53.0.1; };
masterfile-format raw;
file "255types.bk";
}; };

View File

@ -0,0 +1,21 @@
#!/bin/sh
# 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.
# shellcheck source=conf.sh
. ../../conf.sh
for zone in kasp-max-records-per-type \
kasp-max-records-per-type-dnskey \
kasp-max-types-per-name; do
$CHECKZONE -D -F raw -o $zone.db.raw $zone template.db >/dev/null 2>&1
done

View File

@ -0,0 +1,28 @@
; 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 1D
@ IN SOA ns hostmaster (
1
3600
1800
1814400
3
)
NS ns
ns A 10.53.0.1
mx MX 10 mail
a A 10.53.0.1
aaaa AAAA 2001:db8::53
cname CNAME cname-target
dname DNAME dname-target
txt TXT "this is text"

View File

@ -0,0 +1,89 @@
/*
* 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.
*/
// NS4
options {
pid-file "named.pid";
listen-on port @PORT@ { 10.53.0.4; };
port @PORT@;
listen-on-v6 { none; };
recursion no;
notify no;
session-keyfile "session.key";
servfail-ttl 0;
dnssec-validation no;
/* Ridicously low on purpose */
max-records-per-type 1;
max-types-per-name 11;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
dnssec-policy "masterformat" {
keys {
ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
zsk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@;
};
};
/*
* This one should be okay, since the default policy only introduces one DNSKEY
* and each signature covering a different type is considered a separate RRset.
*/
zone "kasp-max-records-per-type" {
type primary;
file "kasp-max-records-per-type.db.raw";
masterfile-format raw;
dnssec-policy "default";
inline-signing no;
allow-update { any; };
allow-transfer { any; };
};
/*
* This one uses a ZSK / KSK, so that is two records in one RRset,
* thus it should fail to sign.
*/
zone "kasp-max-records-per-type-dnskey" {
type primary;
file "kasp-max-records-per-type-dnskey.db.raw";
masterfile-format raw;
dnssec-policy "masterformat";
inline-signing no;
allow-update { any; };
allow-transfer { any; };
};
/*
* The template zone is fine and should be possible to sign, but when
* adding an extra type to the apex the max-types-per-name will be exceeded,
* meaning the update should fail.
*/
zone "kasp-max-types-per-name" {
type primary;
file "kasp-max-types-per-name.db.raw";
masterfile-format raw;
dnssec-policy "default";
inline-signing no;
allow-update { any; };
allow-transfer { any; };
};

View File

@ -0,0 +1,53 @@
/*
* 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.
*/
// NS4
options {
pid-file "named.pid";
listen-on port @PORT@ { 10.53.0.4; };
port @PORT@;
listen-on-v6 { none; };
recursion no;
notify no;
session-keyfile "session.key";
servfail-ttl 0;
dnssec-validation no;
/* Ridicously low on purpose */
max-records-per-type 1;
max-types-per-name 9;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
/*
* The template zone is fine, but when adding the DNSSEC records to the apex,
* the max-types-per-name will be exceeded, meaning signing should fail.
*/
zone "kasp-max-types-per-name" {
type primary;
file "kasp-max-types-per-name.db.raw";
masterfile-format raw;
dnssec-policy "default";
inline-signing no;
allow-update { any; };
allow-transfer { any; };
};

View File

@ -0,0 +1,28 @@
; 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 1D
@ IN SOA ns hostmaster (
1
3600
1800
1814400
3
)
NS ns
ns A 10.53.0.1
mx MX 10 mail
a A 10.53.0.1
aaaa AAAA 2001:db8::53
cname CNAME cname-target
dname DNAME dname-target
txt TXT "this is text"

View File

@ -19,13 +19,44 @@ $SHELL clean.sh
copy_setports ns1/named.conf.in ns1/named.conf copy_setports ns1/named.conf.in ns1/named.conf
copy_setports ns2/named.conf.in ns2/named.conf copy_setports ns2/named.conf.in ns2/named.conf
copy_setports ns3/named.conf.in ns3/named.conf copy_setports ns3/named.conf.in ns3/named.conf
copy_setports ns4/named1.conf.in ns4/named.conf
cp ns1/example.db ns2/ cp ns1/example.db ns2/
cp ns2/formerly-text.db.in ns2/formerly-text.db cp ns2/formerly-text.db.in ns2/formerly-text.db
cp ns1/large.db.in ns1/large.db cp ns1/empty.db.in ns1/under-limit.db
# counts are set with respect to these limits in named.conf:
# max-records-per-type 2050;
# max-types-per-name 500;
awk 'END { awk 'END {
for (i = 0; i < 512; i++ ) { print "a TXT", i; } for (i = 0; i < 500; i++ ) { print "500-txt TXT", i; }
for (i = 0; i < 1024; i++ ) { print "b TXT", i; } for (i = 0; i < 1000; i++ ) { print "1000-txt TXT", i; }
for (i = 0; i < 2000; i++ ) { print "c TXT", i; } for (i = 0; i < 2000; i++ ) { print "2000-txt TXT", i; }
}' </dev/null >>ns1/large.db }' </dev/null >>ns1/under-limit.db
cd ns1 && $SHELL compile.sh cp ns1/under-limit.db ns1/under-limit-kasp.db
cp ns1/empty.db.in ns1/on-limit.db
awk 'END {
for (i = 0; i < 500; i++ ) { print "500-txt TXT", i; }
for (i = 0; i < 1000; i++ ) { print "1000-txt TXT", i; }
for (i = 0; i < 2000; i++ ) { print "2000-txt TXT", i; }
for (i = 0; i < 2050; i++ ) { print "2050-txt TXT", i; }
}' </dev/null >>ns1/on-limit.db
cp ns1/on-limit.db ns1/on-limit-kasp.db
cp ns1/empty.db.in ns1/over-limit.db
awk 'END {
for (i = 0; i < 500; i++ ) { print "500-txt TXT", i; }
for (i = 0; i < 1000; i++ ) { print "1000-txt TXT", i; }
for (i = 0; i < 2000; i++ ) { print "2000-txt TXT", i; }
for (i = 0; i < 2050; i++ ) { print "2050-txt TXT", i; }
for (i = 0; i < 2100; i++ ) { print "2100-txt TXT", i; }
}' </dev/null >>ns1/over-limit.db
cp ns1/empty.db.in ns1/255types.db
for ntype in $(seq 65280 65534); do
echo "m TYPE${ntype} \# 0"
done >>ns1/255types.db
echo "m TXT bunny" >>ns1/255types.db
(cd ns1 && $SHELL compile.sh)
(cd ns4 && $SHELL compile.sh)

View File

@ -134,7 +134,7 @@ n=$((n + 1))
status=$((status + ret)) status=$((status + ret))
echo_i "waiting for transfers to complete" echo_i "waiting for transfers to complete"
for i in 0 1 2 3 4 5 6 7 8 9; do for _attempt in 0 1 2 3 4 5 6 7 8 9; do
test -f ns2/transfer.db.raw -a -f ns2/transfer.db.txt && break test -f ns2/transfer.db.raw -a -f ns2/transfer.db.txt && break
sleep 1 sleep 1
done done
@ -162,7 +162,7 @@ n=$((n + 1))
status=$((status + ret)) status=$((status + ret))
echo_i "checking that secondary formerly in text format is now raw ($n)" echo_i "checking that secondary formerly in text format is now raw ($n)"
for i in 0 1 2 3 4 5 6 7 8 9; do for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0 ret=0
israw ns2/formerly-text.db >/dev/null 2>&1 || ret=1 israw ns2/formerly-text.db >/dev/null 2>&1 || ret=1
[ "$(rawversion ns2/formerly-text.db)" -eq 1 ] || ret=1 [ "$(rawversion ns2/formerly-text.db)" -eq 1 ] || ret=1
@ -173,12 +173,12 @@ n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed" [ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret)) status=$((status + ret))
echo_i "checking that large rdatasets loaded ($n)" echo_i "checking that under-limit rdatasets loaded ($n)"
for i in 0 1 2 3 4 5 6 7 8 9; do for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0 ret=0
for a in a b c; do for rrcount in 500-txt 1000-txt 2000-txt; do
$DIG +tcp txt "${a}.large" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.test$n" $DIG +tcp txt "${rrcount}.under-limit" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns2.test$n" >/dev/null || ret=1 grep "status: NOERROR" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done done
[ $ret -eq 0 ] && break [ $ret -eq 0 ] && break
sleep 1 sleep 1
@ -187,6 +187,253 @@ n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed" [ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret)) status=$((status + ret))
echo_i "checking that under-limit rdatasets transfered ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt; do
$DIG +tcp txt "${rrcount}.under-limit" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns2.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that under-limit-kasp dnskeys loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
$DIG +tcp +dnssec dnskey "under-limit-kasp" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.dnskey.test$n"
grep "status: NOERROR" "dig.out.ns1.dnskey.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns1.dnskey.test$n" >/dev/null || ret=1
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that under-limit-kasp rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt; do
$DIG +tcp +dnssec txt "${rrcount}.under-limit-kasp" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that under-limit-kasp rdatasets transfered ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt; do
$DIG +tcp +dnssec txt "${rrcount}.under-limit-kasp" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns2.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that on-limit rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt; do
$DIG +tcp txt "${rrcount}.on-limit" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that on-limit rdatasets not transfered ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt; do
$DIG +tcp txt "${rrcount}.on-limit" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.$rrcount.test$n"
grep "status: SERVFAIL" "dig.out.ns2.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that on-limit-kasp rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt; do
$DIG +tcp +dnssec txt "${rrcount}.on-limit-kasp" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that on-limit-kasp rdatasets not transfered ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt; do
$DIG +tcp +dnssec txt "${rrcount}.on-limit-kasp" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.$rrcount.test$n"
grep "status: SERVFAIL" "dig.out.ns2.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that over-limit rdatasets not loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt 2100-txt; do
$DIG +tcp txt "${rrcount}.over-limit" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: SERVFAIL" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that kasp-max-records-per-type rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrtype in soa dnskey ns; do
$DIG +tcp +dnssec $rrtype "kasp-max-records-per-type" @10.53.0.4 -p "${PORT}" >"dig.out.ns4.$rrtype.test$n"
grep "status: NOERROR" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that kasp-max-records-per-type-dnskey rdatasets not signed ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrtype in soa dnskey ns; do
$DIG +tcp +dnssec $rrtype "kasp-max-records-per-type-dnskey" @10.53.0.4 -p "${PORT}" >"dig.out.ns4.$rrtype.test$n"
grep "status: NOERROR" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns4.$rrtype.test$n" >/dev/null && ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that kasp-max-types-per-name rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrtype in soa dnskey ns; do
$DIG +tcp +dnssec $rrtype "kasp-max-types-per-name" @10.53.0.4 -p "${PORT}" >"dig.out.ns4.$rrtype.test$n"
grep "status: NOERROR" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
# Update zone with nsupdate.
n=$((n + 1))
echo_i "add new type to zone and check that it fails ($n)"
ret=0
(
echo zone kasp-max-types-per-name.
echo server 10.53.0.4 "$PORT"
echo update add kasp-max-types-per-name. 300 TXT KAPUTT
echo send
) | $NSUPDATE && ret=1
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that kasp-max-types-per-name rdatasets loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrtype in soa dnskey ns txt; do
$DIG +tcp +dnssec $rrtype "kasp-max-types-per-name" @10.53.0.4 -p "${PORT}" >"dig.out.ns4.$rrtype.test$n"
grep "status: NOERROR" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
grep "KAPUTT" "dig.out.ns4.$rrtype.test$n" >/dev/null && ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
# Reconfigure ns4
echo_i "reconfigure ns4"
stop_server ns4
copy_setports ns4/named2.conf.in ns4/named.conf
# Recompile zone
$CHECKZONE -D -F raw -o ns4/kasp.db.raw kasp-max-types-per-name ns4/template.db >/dev/null 2>&1
start_server --noclean --restart --port "${PORT}" ns4
echo_i "checking that kasp-max-types-per-name rdatasets not loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrtype in soa dnskey ns; do
$DIG +tcp +dnssec $rrtype "kasp-max-types-per-name" @10.53.0.4 -p "${PORT}" >"dig.out.ns4.$rrtype.test$n"
grep "status: SERVFAIL" "dig.out.ns4.$rrtype.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that 255 types are loaded ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
$DIG +tcp TXT "m.255types" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.test$n"
grep "status: NOERROR" "dig.out.ns1.test$n" >/dev/null || ret=1
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking that 255 types types are not transfered ($n)"
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
$DIG +tcp TXT "m.255types" @10.53.0.2 -p "${PORT}" >"dig.out.ns2.test$n"
grep "status: SERVFAIL" "dig.out.ns2.test$n" >/dev/null || ret=1
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "checking format transitions: text->raw->text ($n)" echo_i "checking format transitions: text->raw->text ($n)"
ret=0 ret=0
$CHECKZONE -D -f text -F text -o baseline.txt example.nil ns1/example.db >/dev/null $CHECKZONE -D -f text -F text -o baseline.txt example.nil ns1/example.db >/dev/null
@ -240,7 +487,7 @@ stop_server --use-rndc --port ${CONTROLPORT} ns3
rm ns3/*.jnl rm ns3/*.jnl
restart restart
#shellcheck disable=SC2034 #shellcheck disable=SC2034
for i in 0 1 2 3 4 5 6 7 8 9; do for _attempt in 0 1 2 3 4 5 6 7 8 9; do
lret=0 lret=0
dig_with_opts +comm @10.53.0.3 moretext.dynamic txt >"dig.out.dynamic2.ns3.test$n" dig_with_opts +comm @10.53.0.3 moretext.dynamic txt >"dig.out.dynamic2.ns3.test$n"
grep "more text" "dig.out.dynamic2.ns3.test$n" >/dev/null 2>&1 || lret=1 grep "more text" "dig.out.dynamic2.ns3.test$n" >/dev/null 2>&1 || lret=1
@ -273,5 +520,24 @@ n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed" [ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret)) status=$((status + ret))
echo_i "checking that on-limit-kasp rdatasets loaded after re-sign and re-start ($n)"
ret=0
stop_server ns1
start_server --noclean --restart --port "${PORT}" ns1
for _attempt in 0 1 2 3 4 5 6 7 8 9; do
ret=0
for rrcount in 500-txt 1000-txt 2000-txt 2050-txt; do
$DIG +tcp +dnssec txt "${rrcount}.on-limit-kasp" @10.53.0.1 -p "${PORT}" >"dig.out.ns1.$rrcount.test$n"
grep "status: NOERROR" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
grep "RRSIG" "dig.out.ns1.$rrcount.test$n" >/dev/null || ret=1
done
[ $ret -eq 0 ] && break
sleep 1
done
n=$((n + 1))
[ $ret -eq 0 ] || echo_i "failed"
status=$((status + ret))
echo_i "exit status: $status" echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1 [ $status -eq 0 ] || exit 1

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,13 @@ options {
listen-on-v6 { none; }; listen-on-v6 { none; };
recursion no; recursion no;
dnssec-validation no; dnssec-validation no;
max-records-per-type 0;
max-types-per-name 0;
}; };
zone "." { type primary; file "root.db"; }; zone "." { type primary; file "root.db"; };
zone "big." {
type primary;
file "big.db";
};

View File

@ -19,3 +19,6 @@ example.net. 60 IN NS direct.example.net.
direct.example.net. 60 IN A 10.53.0.2 direct.example.net. 60 IN A 10.53.0.2
example.com. 60 IN NS direct.example.com. example.com. 60 IN NS direct.example.com.
direct.example.com. 60 IN A 10.53.0.4 direct.example.com. 60 IN A 10.53.0.4
big. in NS ns.big.
ns.big. 60 IN A 10.53.0.1

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
options {
directory ".";
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
servfail-ttl 0;
qname-minimization disabled;
max-recursion-depth 12;
recursion yes;
dnssec-validation yes;
max-records-per-type 0;
max-types-per-name 10;
};
trust-anchors { };
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." { type hint; file "hints.db"; };

View File

@ -0,0 +1,43 @@
/*
* 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.
*/
options {
directory ".";
query-source address 10.53.0.3;
notify-source 10.53.0.3;
transfer-source 10.53.0.3;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.3; };
listen-on-v6 { none; };
servfail-ttl 0;
qname-minimization disabled;
max-recursion-depth 12;
recursion yes;
dnssec-validation yes;
max-records-per-type 0;
max-types-per-name 0;
};
trust-anchors { };
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." { type hint; file "hints.db"; };

View File

@ -222,6 +222,50 @@ eval count=$(cat dig.out.3.test$n)
if [ $ret != 0 ]; then echo_i "failed"; fi if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret)) status=$((status + ret))
#grep "duplicate query" ns3/named.run n=$((n + 1))
echo_i "checking RRset that exceeds max-records-per-type ($n)"
ret=0
$DIG $DIGOPTS @10.53.0.3 biganswer.big >dig.out.1.test$n || ret=1
grep 'status: SERVFAIL' dig.out.1.test$n >/dev/null || ret=1
ns3_reset ns3/named5.conf.in
$DIG $DIGOPTS @10.53.0.3 biganswer.big >dig.out.2.test$n || ret=1
grep 'status: NOERROR' dig.out.2.test$n >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
check_manytypes() (
i=$1
type=$2
expected=$3
$DIG $DIGOPTS @10.53.0.3 IN $type manytypes.big >dig.out.$i.$type.test$n || exit 1
grep 'status: '"${expected}"'' dig.out.$i.$type.test$n >/dev/null || exit 1
exit 0
)
n=$((n + 1))
echo_i "checking name that exceeds max-types-per-name ($n)"
ret=0
# Limited to 10 types - these should be fine
for ntype in $(seq 65280 65289); do
check_manytypes 1 "TYPE${ntype}" NOERROR || ret=1
done
# Everything on top of that should SERVFAIL
for ntype in $(seq 65290 65534); do
check_manytypes 1 "TYPE${ntype}" SERVFAIL || ret=1
done
# Lift the limit
ns3_reset ns3/named6.conf.in
for ntype in $(seq 65280 65534); do
check_manytypes 2 "TYPE${ntype}" NOERROR || ret=1
done
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status" echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1 [ $status -eq 0 ] || exit 1

View File

@ -3681,6 +3681,36 @@ system.
This sets the maximum number of records permitted in a zone. The default is This sets the maximum number of records permitted in a zone. The default is
zero, which means the maximum is unlimited. zero, which means the maximum is unlimited.
.. namedconf:statement:: max-records-per-type
:tags: server
:short: Sets the maximum number of records that can be stored in an RRset
This sets the maximum number of resource records that can be stored
in an RRset in a database. When configured in :namedconf:ref:`options`
or :namedconf:ref:`view`, it controls the cache database; it also sets
the default value for zone databases, which can be overridden by setting
it at the :namedconf:ref:`zone` level.
If set to a positive value, any attempt to cache or to add to a zone
an RRset with more than the specified number of records will result in
a failure. If set to 0, there is no cap on RRset size. The default is
100.
.. namedconf:statement:: max-types-per-name
:tags: server
:short: Sets the maximum number of RR types that can be stored for an owner name
This sets the maximum number of resource record types that can be stored
for a single owner name in a database. When configured in :namedconf:ref:`options`
or :namedconf:ref:`view`, it controls the cache database, and also sets
the default value for zone databases, which can be overridden by setting
it at the :namedconf:ref:`zone` level
If set to a positive value, any attempt to cache or to add to a zone an owner
name with more than the specified number of resource record types will result
in a failure. If set to 0, there is no cap on RR types number. The default is
100.
.. namedconf:statement:: recursive-clients .. namedconf:statement:: recursive-clients
:tags: query :tags: query
:short: Specifies the maximum number of concurrent recursive queries the server can perform. :short: Specifies the maximum number of concurrent recursive queries the server can perform.

View File

@ -16,12 +16,14 @@ zone <string> [ <class> ] {
max-ixfr-ratio ( unlimited | <percentage> ); max-ixfr-ratio ( unlimited | <percentage> );
max-journal-size ( default | unlimited | <sizeval> ); max-journal-size ( default | unlimited | <sizeval> );
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-refresh-time <integer>; max-refresh-time <integer>;
max-retry-time <integer>; max-retry-time <integer>;
max-transfer-idle-in <integer>; max-transfer-idle-in <integer>;
max-transfer-idle-out <integer>; max-transfer-idle-out <integer>;
max-transfer-time-in <integer>; max-transfer-time-in <integer>;
max-transfer-time-out <integer>; max-transfer-time-out <integer>;
max-types-per-name <integer>;
min-refresh-time <integer>; min-refresh-time <integer>;
min-retry-time <integer>; min-retry-time <integer>;
multi-master <boolean>; multi-master <boolean>;

View File

@ -183,6 +183,7 @@ options {
max-journal-size ( default | unlimited | <sizeval> ); max-journal-size ( default | unlimited | <sizeval> );
max-ncache-ttl <duration>; max-ncache-ttl <duration>;
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-recursion-depth <integer>; max-recursion-depth <integer>;
max-recursion-queries <integer>; max-recursion-queries <integer>;
max-refresh-time <integer>; max-refresh-time <integer>;
@ -193,6 +194,7 @@ options {
max-transfer-idle-out <integer>; max-transfer-idle-out <integer>;
max-transfer-time-in <integer>; max-transfer-time-in <integer>;
max-transfer-time-out <integer>; max-transfer-time-out <integer>;
max-types-per-name <integer>;
max-udp-size <integer>; max-udp-size <integer>;
max-validation-failures-per-fetch <integer>; // experimental max-validation-failures-per-fetch <integer>; // experimental
max-validations-per-fetch <integer>; // experimental max-validations-per-fetch <integer>; // experimental
@ -468,6 +470,7 @@ view <string> [ <class> ] {
max-journal-size ( default | unlimited | <sizeval> ); max-journal-size ( default | unlimited | <sizeval> );
max-ncache-ttl <duration>; max-ncache-ttl <duration>;
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-recursion-depth <integer>; max-recursion-depth <integer>;
max-recursion-queries <integer>; max-recursion-queries <integer>;
max-refresh-time <integer>; max-refresh-time <integer>;
@ -477,6 +480,7 @@ view <string> [ <class> ] {
max-transfer-idle-out <integer>; max-transfer-idle-out <integer>;
max-transfer-time-in <integer>; max-transfer-time-in <integer>;
max-transfer-time-out <integer>; max-transfer-time-out <integer>;
max-types-per-name <integer>;
max-udp-size <integer>; max-udp-size <integer>;
max-validation-failures-per-fetch <integer>; // experimental max-validation-failures-per-fetch <integer>; // experimental
max-validations-per-fetch <integer>; // experimental max-validations-per-fetch <integer>; // experimental

View File

@ -37,8 +37,10 @@ zone <string> [ <class> ] {
max-ixfr-ratio ( unlimited | <percentage> ); max-ixfr-ratio ( unlimited | <percentage> );
max-journal-size ( default | unlimited | <sizeval> ); max-journal-size ( default | unlimited | <sizeval> );
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-transfer-idle-out <integer>; max-transfer-idle-out <integer>;
max-transfer-time-out <integer>; max-transfer-time-out <integer>;
max-types-per-name <integer>;
max-zone-ttl ( unlimited | <duration> ); // deprecated max-zone-ttl ( unlimited | <duration> ); // deprecated
notify ( explicit | master-only | primary-only | <boolean> ); notify ( explicit | master-only | primary-only | <boolean> );
notify-delay <integer>; notify-delay <integer>;

View File

@ -7,6 +7,8 @@ zone <string> [ <class> ] {
masterfile-format ( raw | text ); masterfile-format ( raw | text );
masterfile-style ( full | relative ); masterfile-style ( full | relative );
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-types-per-name <integer>;
max-zone-ttl ( unlimited | <duration> ); // deprecated max-zone-ttl ( unlimited | <duration> ); // deprecated
primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... }; primaries [ port <integer> ] [ source ( <ipv4_address> | * ) ] [ source-v6 ( <ipv6_address> | * ) ] { ( <remote-servers> | <ipv4_address> [ port <integer> ] | <ipv6_address> [ port <integer> ] ) [ key <string> ] [ tls <string> ]; ... };
zone-statistics ( full | terse | none | <boolean> ); zone-statistics ( full | terse | none | <boolean> );

View File

@ -28,12 +28,14 @@ zone <string> [ <class> ] {
max-ixfr-ratio ( unlimited | <percentage> ); max-ixfr-ratio ( unlimited | <percentage> );
max-journal-size ( default | unlimited | <sizeval> ); max-journal-size ( default | unlimited | <sizeval> );
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-refresh-time <integer>; max-refresh-time <integer>;
max-retry-time <integer>; max-retry-time <integer>;
max-transfer-idle-in <integer>; max-transfer-idle-in <integer>;
max-transfer-idle-out <integer>; max-transfer-idle-out <integer>;
max-transfer-time-in <integer>; max-transfer-time-in <integer>;
max-transfer-time-out <integer>; max-transfer-time-out <integer>;
max-types-per-name <integer>;
min-refresh-time <integer>; min-refresh-time <integer>;
min-retry-time <integer>; min-retry-time <integer>;
multi-master <boolean>; multi-master <boolean>;

View File

@ -5,6 +5,8 @@ zone <string> [ <class> ] {
forward ( first | only ); forward ( first | only );
forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... }; forwarders [ port <integer> ] [ tls <string> ] { ( <ipv4_address> | <ipv6_address> ) [ port <integer> ] [ tls <string> ]; ... };
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-types-per-name <integer>;
server-addresses { ( <ipv4_address> | <ipv6_address> ); ... }; server-addresses { ( <ipv4_address> | <ipv6_address> ); ... };
server-names { <string>; ... }; server-names { <string>; ... };
zone-statistics ( full | terse | none | <boolean> ); zone-statistics ( full | terse | none | <boolean> );

View File

@ -11,10 +11,12 @@ zone <string> [ <class> ] {
masterfile-format ( raw | text ); masterfile-format ( raw | text );
masterfile-style ( full | relative ); masterfile-style ( full | relative );
max-records <integer>; max-records <integer>;
max-records-per-type <integer>;
max-refresh-time <integer>; max-refresh-time <integer>;
max-retry-time <integer>; max-retry-time <integer>;
max-transfer-idle-in <integer>; max-transfer-idle-in <integer>;
max-transfer-time-in <integer>; max-transfer-time-in <integer>;
max-types-per-name <integer>;
min-refresh-time <integer>; min-refresh-time <integer>;
min-retry-time <integer>; min-retry-time <integer>;
multi-master <boolean>; multi-master <boolean>;

View File

@ -19,6 +19,21 @@ Security Fixes
responses can cause server to respond slowly or not respond at all for other responses can cause server to respond slowly or not respond at all for other
clients. :cve:`2024-0760` :gl:`#4481` clients. :cve:`2024-0760` :gl:`#4481`
- Excessively large resource record sets can be crafted to slow down
database processing. This has been addressed by adding a configurable
limit to the number of records that can be stored per name and type in
a cache or zone database. The default is 100, but it can be tuned with
the new ``max-records-per-type`` option. :gl:`#497` :gl:`#3405`
An excessively large number of resource record types for a single owner name can
be crafted to slow down database processing. This has been addressed by adding
a configurable limit to the number of records that can be stored per name and
type in a cache or zone database. The default is 100, and can be tuned with
the new ``max-rrtypes-per-name`` option. :cve:`2024-1737` :gl:`#3403`
ISC would like to thank Toshifumi Sakaguchi who independently discovered
and responsibly reported the issue to ISC. :gl:`#4548`
New Features New Features
~~~~~~~~~~~~ ~~~~~~~~~~~~

View File

@ -80,6 +80,8 @@ struct dns_cache {
dns_ttl_t serve_stale_ttl; dns_ttl_t serve_stale_ttl;
dns_ttl_t serve_stale_refresh; dns_ttl_t serve_stale_refresh;
isc_stats_t *stats; isc_stats_t *stats;
uint32_t maxrrperset;
uint32_t maxtypepername;
}; };
/*** /***
@ -128,6 +130,8 @@ cache_create_db(dns_cache_t *cache, dns_db_t **dbp, isc_mem_t **tmctxp,
dns_db_setservestalettl(db, cache->serve_stale_ttl); dns_db_setservestalettl(db, cache->serve_stale_ttl);
dns_db_setservestalerefresh(db, cache->serve_stale_refresh); dns_db_setservestalerefresh(db, cache->serve_stale_refresh);
dns_db_setmaxrrperset(db, cache->maxrrperset);
dns_db_setmaxtypepername(db, cache->maxtypepername);
/* /*
* XXX this is only used by the RBT cache, and can * XXX this is only used by the RBT cache, and can
@ -546,6 +550,26 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result) {
} }
} }
void
dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value) {
REQUIRE(VALID_CACHE(cache));
cache->maxrrperset = value;
if (cache->db != NULL) {
dns_db_setmaxrrperset(cache->db, value);
}
}
void
dns_cache_setmaxtypepername(dns_cache_t *cache, uint32_t value) {
REQUIRE(VALID_CACHE(cache));
cache->maxtypepername = value;
if (cache->db != NULL) {
dns_db_setmaxtypepername(cache->db, value);
}
}
/* /*
* XXX: Much of the following code has been copied in from statschannel.c. * XXX: Much of the following code has been copied in from statschannel.c.
* We should refactor this into a generic function in stats.c that can be * We should refactor this into a generic function in stats.c that can be

View File

@ -1170,3 +1170,21 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name) {
} }
return (ISC_R_NOTIMPLEMENTED); return (ISC_R_NOTIMPLEMENTED);
} }
void
dns_db_setmaxrrperset(dns_db_t *db, uint32_t value) {
REQUIRE(DNS_DB_VALID(db));
if (db->methods->setmaxrrperset != NULL) {
(db->methods->setmaxrrperset)(db, value);
}
}
void
dns_db_setmaxtypepername(dns_db_t *db, uint32_t value) {
REQUIRE(DNS_DB_VALID(db));
if (db->methods->setmaxtypepername != NULL) {
(db->methods->setmaxtypepername)(db, value);
}
}

View File

@ -246,6 +246,18 @@ dns_cache_updatestats(dns_cache_t *cache, isc_result_t result);
* Update cache statistics based on result code in 'result' * Update cache statistics based on result code in 'result'
*/ */
void
dns_cache_setmaxrrperset(dns_cache_t *cache, uint32_t value);
/*%<
* Set the maximum resource records per RRSet that can be cached.
*/
void
dns_cache_setmaxtypepername(dns_cache_t *cache, uint32_t value);
/*%<
* Set the maximum resource record types per owner name that can be cached.
*/
#ifdef HAVE_LIBXML2 #ifdef HAVE_LIBXML2
int int
dns_cache_renderxml(dns_cache_t *cache, void *writer0); dns_cache_renderxml(dns_cache_t *cache, void *writer0);

View File

@ -183,6 +183,8 @@ typedef struct dns_dbmethods {
void (*deletedata)(dns_db_t *db, dns_dbnode_t *node, void *data); void (*deletedata)(dns_db_t *db, dns_dbnode_t *node, void *data);
isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node, isc_result_t (*nodefullname)(dns_db_t *db, dns_dbnode_t *node,
dns_name_t *name); dns_name_t *name);
void (*setmaxrrperset)(dns_db_t *db, uint32_t value);
void (*setmaxtypepername)(dns_db_t *db, uint32_t value);
} dns_dbmethods_t; } dns_dbmethods_t;
typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx, typedef isc_result_t (*dns_dbcreatefunc_t)(isc_mem_t *mctx,
@ -1800,4 +1802,23 @@ dns_db_nodefullname(dns_db_t *db, dns_dbnode_t *node, dns_name_t *name);
* \li 'db' is a valid database * \li 'db' is a valid database
* \li 'node' and 'name' are not NULL * \li 'node' and 'name' are not NULL
*/ */
void
dns_db_setmaxrrperset(dns_db_t *db, uint32_t value);
/*%<
* Set the maximum permissible number of RRs per RRset.
*
* If 'value' is nonzero, then any subsequent attempt to add an rdataset
* with more than 'value' RRs will return ISC_R_TOOMANYRECORDS.
*/
void
dns_db_setmaxtypepername(dns_db_t *db, uint32_t value);
/*%<
* Set the maximum permissible number of RR types per owner name.
*
* If 'value' is nonzero, and if there are already 'value' RR types
* stored at a given node, then any subsequent attempt to add an rdataset
* with a new RR type will return ISC_R_TOOMANYRECORDS.
*/
ISC_LANG_ENDDECLS ISC_LANG_ENDDECLS

View File

@ -169,7 +169,8 @@ extern dns_rdatasetmethods_t dns_rdataslab_rdatasetmethods;
isc_result_t isc_result_t
dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
isc_region_t *region, unsigned int reservelen); isc_region_t *region, unsigned int reservelen,
uint32_t limit);
/*%< /*%<
* Slabify a rdataset. The slab area will be allocated and returned * Slabify a rdataset. The slab area will be allocated and returned
* in 'region'. * in 'region'.
@ -225,7 +226,8 @@ isc_result_t
dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
unsigned int reservelen, isc_mem_t *mctx, unsigned int reservelen, isc_mem_t *mctx,
dns_rdataclass_t rdclass, dns_rdatatype_t type, dns_rdataclass_t rdclass, dns_rdatatype_t type,
unsigned int flags, unsigned char **tslabp); unsigned int flags, uint32_t maxrrperset,
unsigned char **tslabp);
/*%< /*%<
* Merge 'oslab' and 'nslab'. * Merge 'oslab' and 'nslab'.
*/ */

View File

@ -183,6 +183,8 @@ struct dns_view {
uint32_t fail_ttl; uint32_t fail_ttl;
dns_badcache_t *failcache; dns_badcache_t *failcache;
unsigned int udpsize; unsigned int udpsize;
uint32_t maxrrperset;
uint32_t maxtypepername;
/* /*
* Configurable data for server use only, * Configurable data for server use only,
@ -1242,6 +1244,18 @@ dns_view_getresolver(dns_view_t *view, dns_resolver_t **resolverp);
* Return the resolver associated with the view. * Return the resolver associated with the view.
*/ */
void
dns_view_setmaxrrperset(dns_view_t *view, uint32_t value);
/*%<
* Set the maximum resource records per RRSet that can be cached.
*/
void
dns_view_setmaxtypepername(dns_view_t *view, uint32_t value);
/*%<
* Set the maximum resource record types per owner name that can be cached.
*/
void void
dns_view_setudpsize(dns_view_t *view, uint16_t udpsize); dns_view_setudpsize(dns_view_t *view, uint16_t udpsize);
/*%< /*%<

View File

@ -366,6 +366,32 @@ dns_zone_getmaxrecords(dns_zone_t *zone);
*\li uint32_t maxrecords. *\li uint32_t maxrecords.
*/ */
void
dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t maxrrperset);
/*%<
* Sets the maximum number of records per rrset permitted in a zone.
* 0 implies unlimited.
*
* Requires:
*\li 'zone' to be valid initialised zone.
*
* Returns:
*\li void
*/
void
dns_zone_setmaxtypepername(dns_zone_t *zone, uint32_t maxtypepername);
/*%<
* Sets the maximum number of resource record types per owner name
* permitted in a zone. 0 implies unlimited.
*
* Requires:
*\li 'zone' to be valid initialised zone.
*
* Returns:
*\li void
*/
void void
dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl); dns_zone_setmaxttl(dns_zone_t *zone, uint32_t maxttl);
/*%< /*%<

View File

@ -217,6 +217,9 @@ struct qpcache {
/* Locked by lock. */ /* Locked by lock. */
unsigned int active; unsigned int active;
uint32_t maxrrperset; /* Maximum RRs per RRset */
uint32_t maxtypepername; /* Maximum number of RR types per owner */
/* /*
* The time after a failed lookup, where stale answers from cache * The time after a failed lookup, where stale answers from cache
* may be used directly in a DNS response without attempting a * may be used directly in a DNS response without attempting a
@ -2883,6 +2886,7 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode,
dns_typepair_t negtype = 0, sigtype; dns_typepair_t negtype = 0, sigtype;
dns_trust_t trust; dns_trust_t trust;
int idx; int idx;
uint32_t ntypes;
if ((options & DNS_DBADD_FORCE) != 0) { if ((options & DNS_DBADD_FORCE) != 0) {
trust = dns_trust_ultimate; trust = dns_trust_ultimate;
@ -2915,6 +2919,7 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode,
{ {
mark_ancient(topheader); mark_ancient(topheader);
} }
ntypes = 0; /* Always add the negative entry */
goto find_header; goto find_header;
} }
/* /*
@ -2938,9 +2943,11 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode,
* check for an extant non-ancient NODATA ncache * check for an extant non-ancient NODATA ncache
* entry which covers the same type as the RRSIG. * entry which covers the same type as the RRSIG.
*/ */
ntypes = 0;
for (topheader = qpnode->data; topheader != NULL; for (topheader = qpnode->data; topheader != NULL;
topheader = topheader->next) topheader = topheader->next)
{ {
++ntypes;
if ((topheader->type == RDATATYPE_NCACHEANY) || if ((topheader->type == RDATATYPE_NCACHEANY) ||
(newheader->type == sigtype && (newheader->type == sigtype &&
topheader->type == topheader->type ==
@ -2983,9 +2990,12 @@ add(qpcache_t *qpdb, qpcnode_t *qpnode,
} }
} }
ntypes = 0;
for (topheader = qpnode->data; topheader != NULL; for (topheader = qpnode->data; topheader != NULL;
topheader = topheader->next) topheader = topheader->next)
{ {
++ntypes;
if (prio_type(topheader->type)) { if (prio_type(topheader->type)) {
prioheader = topheader; prioheader = topheader;
} }
@ -3253,6 +3263,14 @@ find_header:
/* /*
* No rdatasets of the given type exist at the node. * No rdatasets of the given type exist at the node.
*/ */
if (trust != dns_trust_ultimate &&
qpdb->maxtypepername > 0 &&
ntypes >= qpdb->maxtypepername)
{
dns_slabheader_destroy(&newheader);
return (DNS_R_TOOMANYRECORDS);
}
INSIST(newheader->down == NULL); INSIST(newheader->down == NULL);
if (prio_type(newheader->type)) { if (prio_type(newheader->type)) {
@ -3280,7 +3298,7 @@ find_header:
} }
static isc_result_t static isc_result_t
addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_rdataset_t *rdataset) { dns_rdataset_t *rdataset) {
isc_result_t result; isc_result_t result;
dns_slabheader_proof_t *noqname = NULL; dns_slabheader_proof_t *noqname = NULL;
@ -3291,12 +3309,12 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader,
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
@ -3319,7 +3337,7 @@ cleanup:
} }
static isc_result_t static isc_result_t
addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_rdataset_t *rdataset) { dns_rdataset_t *rdataset) {
isc_result_t result; isc_result_t result;
dns_slabheader_proof_t *closest = NULL; dns_slabheader_proof_t *closest = NULL;
@ -3330,12 +3348,12 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader,
result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
@ -3386,7 +3404,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
} }
result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
qpdb->maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -3423,14 +3442,16 @@ addrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
DNS_SLABHEADER_SETATTR(newheader, DNS_SLABHEADERATTR_OPTOUT); DNS_SLABHEADER_SETATTR(newheader, DNS_SLABHEADERATTR_OPTOUT);
} }
if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) { if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) {
result = addnoqname(qpdb->common.mctx, newheader, rdataset); result = addnoqname(qpdb->common.mctx, newheader,
qpdb->maxrrperset, rdataset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
dns_slabheader_destroy(&newheader); dns_slabheader_destroy(&newheader);
return (result); return (result);
} }
} }
if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) { if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) {
result = addclosest(qpdb->common.mctx, newheader, rdataset); result = addclosest(qpdb->common.mctx, newheader,
qpdb->maxrrperset, rdataset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
dns_slabheader_destroy(&newheader); dns_slabheader_destroy(&newheader);
return (result); return (result);
@ -4330,6 +4351,24 @@ expire_ttl_headers(qpcache_t *qpdb, unsigned int locknum,
} }
} }
static void
setmaxrrperset(dns_db_t *db, uint32_t value) {
qpcache_t *qpdb = (qpcache_t *)db;
REQUIRE(VALID_QPDB(qpdb));
qpdb->maxrrperset = value;
}
static void
setmaxtypepername(dns_db_t *db, uint32_t value) {
qpcache_t *qpdb = (qpcache_t *)db;
REQUIRE(VALID_QPDB(qpdb));
qpdb->maxtypepername = value;
}
static dns_dbmethods_t qpdb_cachemethods = { static dns_dbmethods_t qpdb_cachemethods = {
.destroy = qpdb_destroy, .destroy = qpdb_destroy,
.findnode = findnode, .findnode = findnode,
@ -4354,6 +4393,8 @@ static dns_dbmethods_t qpdb_cachemethods = {
.unlocknode = unlocknode, .unlocknode = unlocknode,
.expiredata = expiredata, .expiredata = expiredata,
.deletedata = deletedata, .deletedata = deletedata,
.setmaxrrperset = setmaxrrperset,
.setmaxtypepername = setmaxtypepername,
}; };
static void static void

View File

@ -178,6 +178,8 @@ struct qpzonedb {
uint32_t current_serial; uint32_t current_serial;
uint32_t least_serial; uint32_t least_serial;
uint32_t next_serial; uint32_t next_serial;
uint32_t maxrrperset; /* Maximum RRs per RRset */
uint32_t maxtypepername; /* Maximum number of RR types per owner */
qpz_version_t *current_version; qpz_version_t *current_version;
qpz_version_t *future_version; qpz_version_t *future_version;
qpz_versionlist_t open_versions; qpz_versionlist_t open_versions;
@ -1833,6 +1835,7 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
unsigned char *merged = NULL; unsigned char *merged = NULL;
isc_result_t result; isc_result_t result;
bool merge = false; bool merge = false;
uint32_t ntypes;
if ((options & DNS_DBADD_MERGE) != 0) { if ((options & DNS_DBADD_MERGE) != 0) {
REQUIRE(version != NULL); REQUIRE(version != NULL);
@ -1848,9 +1851,11 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
changed = add_changed(newheader, version DNS__DB_FLARG_PASS); changed = add_changed(newheader, version DNS__DB_FLARG_PASS);
} }
ntypes = 0;
for (topheader = node->data; topheader != NULL; for (topheader = node->data; topheader != NULL;
topheader = topheader->next) topheader = topheader->next)
{ {
++ntypes;
if (prio_type(topheader->type)) { if (prio_type(topheader->type)) {
prioheader = topheader; prioheader = topheader;
} }
@ -1898,7 +1903,7 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
(unsigned int)(sizeof(*newheader)), (unsigned int)(sizeof(*newheader)),
qpdb->common.mctx, qpdb->common.rdclass, qpdb->common.mctx, qpdb->common.rdclass,
(dns_rdatatype_t)header->type, flags, (dns_rdatatype_t)header->type, flags,
&merged); qpdb->maxrrperset, &merged);
} }
if (result == ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) {
/* /*
@ -2017,6 +2022,14 @@ add(qpzonedb_t *qpdb, qpznode_t *node, const dns_name_t *nodename,
/* /*
* No rdatasets of the given type exist at the node. * No rdatasets of the given type exist at the node.
*/ */
if (qpdb->maxtypepername > 0 &&
ntypes >= qpdb->maxtypepername)
{
dns_slabheader_destroy(&newheader);
return (DNS_R_TOOMANYRECORDS);
}
INSIST(newheader->down == NULL); INSIST(newheader->down == NULL);
if (prio_type(newheader->type)) { if (prio_type(newheader->type)) {
@ -2147,7 +2160,8 @@ loading_addrdataset(void *arg, const dns_name_t *name,
loading_addnode(loadctx, name, rdataset->type, rdataset->covers, &node); loading_addnode(loadctx, name, rdataset->type, rdataset->covers, &node);
result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
qpdb->maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -4648,7 +4662,8 @@ addrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion,
rdataset->covers != dns_rdatatype_nsec3))); rdataset->covers != dns_rdatatype_nsec3)));
result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
qpdb->maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -4767,7 +4782,8 @@ subtractrdataset(dns_db_t *db, dns_dbnode_t *dbnode, dns_dbversion_t *dbversion,
dns_name_copy(&node->name, nodename); dns_name_copy(&node->name, nodename);
result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, qpdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
0);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -5277,6 +5293,24 @@ addglue(dns_db_t *db, dns_dbversion_t *dbversion, dns_rdataset_t *rdataset,
return (ISC_R_SUCCESS); return (ISC_R_SUCCESS);
} }
static void
setmaxrrperset(dns_db_t *db, uint32_t value) {
qpzonedb_t *qpdb = (qpzonedb_t *)db;
REQUIRE(VALID_QPZONE(qpdb));
qpdb->maxrrperset = value;
}
static void
setmaxtypepername(dns_db_t *db, uint32_t value) {
qpzonedb_t *qpdb = (qpzonedb_t *)db;
REQUIRE(VALID_QPZONE(qpdb));
qpdb->maxtypepername = value;
}
static dns_dbmethods_t qpdb_zonemethods = { static dns_dbmethods_t qpdb_zonemethods = {
.destroy = qpdb_destroy, .destroy = qpdb_destroy,
.beginload = beginload, .beginload = beginload,
@ -5310,6 +5344,8 @@ static dns_dbmethods_t qpdb_zonemethods = {
.addglue = addglue, .addglue = addglue,
.deletedata = deletedata, .deletedata = deletedata,
.nodefullname = nodefullname, .nodefullname = nodefullname,
.setmaxrrperset = setmaxrrperset,
.setmaxtypepername = setmaxtypepername,
}; };
static void static void

View File

@ -1582,6 +1582,8 @@ dns_dbmethods_t dns__rbtdb_cachemethods = {
.unlocknode = dns__rbtdb_unlocknode, .unlocknode = dns__rbtdb_unlocknode,
.expiredata = expiredata, .expiredata = expiredata,
.deletedata = dns__rbtdb_deletedata, .deletedata = dns__rbtdb_deletedata,
.setmaxrrperset = dns__rbtdb_setmaxrrperset,
.setmaxtypepername = dns__rbtdb_setmaxtypepername,
}; };
/* /*

View File

@ -1749,7 +1749,8 @@ loading_addrdataset(void *arg, const dns_name_t *name,
} }
result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
rbtdb->maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -2418,6 +2419,8 @@ dns_dbmethods_t dns__rbtdb_zonemethods = {
.addglue = addglue, .addglue = addglue,
.deletedata = dns__rbtdb_deletedata, .deletedata = dns__rbtdb_deletedata,
.nodefullname = dns__rbtdb_nodefullname, .nodefullname = dns__rbtdb_nodefullname,
.setmaxrrperset = dns__rbtdb_setmaxrrperset,
.setmaxtypepername = dns__rbtdb_setmaxtypepername,
}; };
void void

View File

@ -2566,6 +2566,7 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
dns_typepair_t negtype = 0, sigtype; dns_typepair_t negtype = 0, sigtype;
dns_trust_t trust; dns_trust_t trust;
int idx; int idx;
uint32_t ntypes = 0;
if ((options & DNS_DBADD_MERGE) != 0) { if ((options & DNS_DBADD_MERGE) != 0) {
REQUIRE(rbtversion != NULL); REQUIRE(rbtversion != NULL);
@ -2618,6 +2619,7 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
{ {
mark_ancient(topheader); mark_ancient(topheader);
} }
ntypes = 0; /* Always add the negative entry */
goto find_header; goto find_header;
} }
/* /*
@ -2641,9 +2643,11 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
* check for an extant non-ancient NODATA ncache * check for an extant non-ancient NODATA ncache
* entry which covers the same type as the RRSIG. * entry which covers the same type as the RRSIG.
*/ */
ntypes = 0;
for (topheader = rbtnode->data; topheader != NULL; for (topheader = rbtnode->data; topheader != NULL;
topheader = topheader->next) topheader = topheader->next)
{ {
++ntypes;
if ((topheader->type == RDATATYPE_NCACHEANY) || if ((topheader->type == RDATATYPE_NCACHEANY) ||
(newheader->type == sigtype && (newheader->type == sigtype &&
topheader->type == topheader->type ==
@ -2686,9 +2690,11 @@ dns__rbtdb_add(dns_rbtdb_t *rbtdb, dns_rbtnode_t *rbtnode,
} }
} }
ntypes = 0;
for (topheader = rbtnode->data; topheader != NULL; for (topheader = rbtnode->data; topheader != NULL;
topheader = topheader->next) topheader = topheader->next)
{ {
++ntypes;
if (prio_type(topheader->type)) { if (prio_type(topheader->type)) {
prioheader = topheader; prioheader = topheader;
} }
@ -2780,7 +2786,7 @@ find_header:
rbtdb->common.mctx, rbtdb->common.mctx,
rbtdb->common.rdclass, rbtdb->common.rdclass,
(dns_rdatatype_t)header->type, flags, (dns_rdatatype_t)header->type, flags,
&merged); rbtdb->maxrrperset, &merged);
} }
if (result == ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) {
/* /*
@ -3082,6 +3088,14 @@ find_header:
/* /*
* No rdatasets of the given type exist at the node. * No rdatasets of the given type exist at the node.
*/ */
if (rbtdb->maxtypepername > 0 &&
ntypes >= rbtdb->maxtypepername)
{
dns_slabheader_destroy(&newheader);
return (DNS_R_TOOMANYRECORDS);
}
INSIST(newheader->down == NULL); INSIST(newheader->down == NULL);
if (prio_type(newheader->type)) { if (prio_type(newheader->type)) {
@ -3141,7 +3155,7 @@ delegating_type(dns_rbtdb_t *rbtdb, dns_rbtnode_t *node, dns_typepair_t type) {
} }
static isc_result_t static isc_result_t
addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_rdataset_t *rdataset) { dns_rdataset_t *rdataset) {
isc_result_t result; isc_result_t result;
dns_slabheader_proof_t *noqname = NULL; dns_slabheader_proof_t *noqname = NULL;
@ -3152,12 +3166,12 @@ addnoqname(isc_mem_t *mctx, dns_slabheader_t *newheader,
result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig); result = dns_rdataset_getnoqname(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
@ -3180,7 +3194,7 @@ cleanup:
} }
static isc_result_t static isc_result_t
addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader, uint32_t maxrrperset,
dns_rdataset_t *rdataset) { dns_rdataset_t *rdataset) {
isc_result_t result; isc_result_t result;
dns_slabheader_proof_t *closest = NULL; dns_slabheader_proof_t *closest = NULL;
@ -3191,12 +3205,12 @@ addclosest(isc_mem_t *mctx, dns_slabheader_t *newheader,
result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig); result = dns_rdataset_getclosest(rdataset, &name, &neg, &negsig);
RUNTIME_CHECK(result == ISC_R_SUCCESS); RUNTIME_CHECK(result == ISC_R_SUCCESS);
result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0); result = dns_rdataslab_fromrdataset(&neg, mctx, &r1, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0); result = dns_rdataslab_fromrdataset(&negsig, mctx, &r2, 0, maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
goto cleanup; goto cleanup;
} }
@ -3272,7 +3286,8 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node,
} }
result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
rbtdb->maxrrperset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -3329,7 +3344,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node,
} }
if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) { if ((rdataset->attributes & DNS_RDATASETATTR_NOQNAME) != 0) {
result = addnoqname(rbtdb->common.mctx, newheader, result = addnoqname(rbtdb->common.mctx, newheader,
rdataset); rbtdb->maxrrperset, rdataset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
dns_slabheader_destroy(&newheader); dns_slabheader_destroy(&newheader);
return (result); return (result);
@ -3337,7 +3352,7 @@ dns__rbtdb_addrdataset(dns_db_t *db, dns_dbnode_t *node,
} }
if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) { if ((rdataset->attributes & DNS_RDATASETATTR_CLOSEST) != 0) {
result = addclosest(rbtdb->common.mctx, newheader, result = addclosest(rbtdb->common.mctx, newheader,
rdataset); rbtdb->maxrrperset, rdataset);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
dns_slabheader_destroy(&newheader); dns_slabheader_destroy(&newheader);
return (result); return (result);
@ -3487,7 +3502,8 @@ dns__rbtdb_subtractrdataset(dns_db_t *db, dns_dbnode_t *node,
dns__rbtdb_nodefullname(db, node, nodename); dns__rbtdb_nodefullname(db, node, nodename);
result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx, result = dns_rdataslab_fromrdataset(rdataset, rbtdb->common.mctx,
&region, sizeof(dns_slabheader_t)); &region, sizeof(dns_slabheader_t),
0);
if (result != ISC_R_SUCCESS) { if (result != ISC_R_SUCCESS) {
return (result); return (result);
} }
@ -4957,3 +4973,21 @@ expire_ttl_headers(dns_rbtdb_t *rbtdb, unsigned int locknum,
dns_expire_ttl DNS__DB_FLARG_PASS); dns_expire_ttl DNS__DB_FLARG_PASS);
} }
} }
void
dns__rbtdb_setmaxrrperset(dns_db_t *db, uint32_t value) {
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
REQUIRE(VALID_RBTDB(rbtdb));
rbtdb->maxrrperset = value;
}
void
dns__rbtdb_setmaxtypepername(dns_db_t *db, uint32_t maxtypepername) {
dns_rbtdb_t *rbtdb = (dns_rbtdb_t *)db;
REQUIRE(VALID_RBTDB(rbtdb));
rbtdb->maxtypepername = maxtypepername;
}

View File

@ -114,6 +114,8 @@ struct dns_rbtdb {
uint32_t current_serial; uint32_t current_serial;
uint32_t least_serial; uint32_t least_serial;
uint32_t next_serial; uint32_t next_serial;
uint32_t maxrrperset;
uint32_t maxtypepername;
dns_rbtdb_version_t *current_version; dns_rbtdb_version_t *current_version;
dns_rbtdb_version_t *future_version; dns_rbtdb_version_t *future_version;
rbtdb_versionlist_t open_versions; rbtdb_versionlist_t open_versions;
@ -427,6 +429,18 @@ dns__rbtdb_setttl(dns_slabheader_t *header, dns_ttl_t newttl);
* also update the TTL heap accordingly. * also update the TTL heap accordingly.
*/ */
void
dns__rbtdb_setmaxrrperset(dns_db_t *db, uint32_t maxrrperset);
/*%<
* Set the max RRs per RRset limit.
*/
void
dns__rbtdb_setmaxtypepername(dns_db_t *db, uint32_t maxtypepername);
/*%<
* Set the max RRs per RRset limit.
*/
/* /*
* Functions specific to zone databases that are also called from rbtdb.c. * Functions specific to zone databases that are also called from rbtdb.c.
*/ */

View File

@ -168,7 +168,8 @@ fillin_offsets(unsigned char *offsetbase, unsigned int *offsettable,
isc_result_t isc_result_t
dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx, dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
isc_region_t *region, unsigned int reservelen) { isc_region_t *region, unsigned int reservelen,
uint32_t maxrrperset) {
/* /*
* Use &removed as a sentinel pointer for duplicate * Use &removed as a sentinel pointer for duplicate
* rdata as rdata.data == NULL is valid. * rdata as rdata.data == NULL is valid.
@ -208,6 +209,10 @@ dns_rdataslab_fromrdataset(dns_rdataset_t *rdataset, isc_mem_t *mctx,
return (ISC_R_SUCCESS); return (ISC_R_SUCCESS);
} }
if (maxrrperset > 0 && nitems > maxrrperset) {
return (DNS_R_TOOMANYRECORDS);
}
if (nitems > 0xffff) { if (nitems > 0xffff) {
return (ISC_R_NOSPACE); return (ISC_R_NOSPACE);
} }
@ -515,7 +520,8 @@ isc_result_t
dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab, dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
unsigned int reservelen, isc_mem_t *mctx, unsigned int reservelen, isc_mem_t *mctx,
dns_rdataclass_t rdclass, dns_rdatatype_t type, dns_rdataclass_t rdclass, dns_rdatatype_t type,
unsigned int flags, unsigned char **tslabp) { unsigned int flags, uint32_t maxrrperset,
unsigned char **tslabp) {
unsigned char *ocurrent = NULL, *ostart = NULL, *ncurrent = NULL; unsigned char *ocurrent = NULL, *ostart = NULL, *ncurrent = NULL;
unsigned char *tstart = NULL, *tcurrent = NULL, *data = NULL; unsigned char *tstart = NULL, *tcurrent = NULL, *data = NULL;
unsigned int ocount, ncount, count, olength, tlength, tcount, length; unsigned int ocount, ncount, count, olength, tlength, tcount, length;
@ -554,6 +560,10 @@ dns_rdataslab_merge(unsigned char *oslab, unsigned char *nslab,
#endif /* if DNS_RDATASET_FIXED */ #endif /* if DNS_RDATASET_FIXED */
INSIST(ocount > 0 && ncount > 0); INSIST(ocount > 0 && ncount > 0);
if (maxrrperset > 0 && ocount + ncount > maxrrperset) {
return (DNS_R_TOOMANYRECORDS);
}
#if DNS_RDATASET_FIXED #if DNS_RDATASET_FIXED
oncount = ncount; oncount = ncount;
#endif /* if DNS_RDATASET_FIXED */ #endif /* if DNS_RDATASET_FIXED */

View File

@ -643,6 +643,9 @@ dns_view_setcache(dns_view_t *view, dns_cache_t *cache, bool shared) {
dns_cache_attach(cache, &view->cache); dns_cache_attach(cache, &view->cache);
dns_cache_attachdb(cache, &view->cachedb); dns_cache_attachdb(cache, &view->cachedb);
INSIST(DNS_DB_VALID(view->cachedb)); INSIST(DNS_DB_VALID(view->cachedb));
dns_cache_setmaxrrperset(view->cache, view->maxrrperset);
dns_cache_setmaxtypepername(view->cache, view->maxtypepername);
} }
bool bool
@ -2336,6 +2339,24 @@ dns_view_getresolver(dns_view_t *view, dns_resolver_t **resolverp) {
return (result); return (result);
} }
void
dns_view_setmaxrrperset(dns_view_t *view, uint32_t value) {
REQUIRE(DNS_VIEW_VALID(view));
view->maxrrperset = value;
if (view->cache != NULL) {
dns_cache_setmaxrrperset(view->cache, value);
}
}
void
dns_view_setmaxtypepername(dns_view_t *view, uint32_t value) {
REQUIRE(DNS_VIEW_VALID(view));
view->maxtypepername = value;
if (view->cache != NULL) {
dns_cache_setmaxtypepername(view->cache, value);
}
}
void void
dns_view_setudpsize(dns_view_t *view, uint16_t udpsize) { dns_view_setudpsize(dns_view_t *view, uint16_t udpsize) {
REQUIRE(DNS_VIEW_VALID(view)); REQUIRE(DNS_VIEW_VALID(view));

View File

@ -318,6 +318,8 @@ struct dns_zone {
uint32_t minretry; uint32_t minretry;
uint32_t maxrecords; uint32_t maxrecords;
uint32_t maxrrperset;
uint32_t maxtypepername;
dns_remote_t primaries; dns_remote_t primaries;
@ -9741,6 +9743,7 @@ cleanup:
} }
dns_diff_clear(&_sig_diff); dns_diff_clear(&_sig_diff);
dns_diff_clear(&post_diff);
for (i = 0; i < nkeys; i++) { for (i = 0; i < nkeys; i++) {
dst_key_free(&zone_keys[i]); dst_key_free(&zone_keys[i]);
@ -12057,6 +12060,26 @@ dns_zone_setmaxrecords(dns_zone_t *zone, uint32_t val) {
zone->maxrecords = val; zone->maxrecords = val;
} }
void
dns_zone_setmaxrrperset(dns_zone_t *zone, uint32_t val) {
REQUIRE(DNS_ZONE_VALID(zone));
zone->maxrrperset = val;
if (zone->db != NULL) {
dns_db_setmaxrrperset(zone->db, val);
}
}
void
dns_zone_setmaxtypepername(dns_zone_t *zone, uint32_t val) {
REQUIRE(DNS_ZONE_VALID(zone));
zone->maxtypepername = val;
if (zone->db != NULL) {
dns_db_setmaxtypepername(zone->db, val);
}
}
static bool static bool
notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name, notify_isqueued(dns_zone_t *zone, unsigned int flags, dns_name_t *name,
isc_sockaddr_t *addr, dns_tsigkey_t *key, isc_sockaddr_t *addr, dns_tsigkey_t *key,
@ -14458,6 +14481,9 @@ ns_query(dns_zone_t *zone, dns_rdataset_t *soardataset, dns_stub_t *stub) {
goto cleanup; goto cleanup;
} }
dns_db_setloop(stub->db, zone->loop); dns_db_setloop(stub->db, zone->loop);
dns_db_setmaxrrperset(stub->db, zone->maxrrperset);
dns_db_setmaxtypepername(stub->db,
zone->maxtypepername);
} }
result = dns_db_newversion(stub->db, &stub->version); result = dns_db_newversion(stub->db, &stub->version);
@ -17514,6 +17540,8 @@ zone_replacedb(dns_zone_t *zone, dns_db_t *db, bool dump) {
} }
zone_attachdb(zone, db); zone_attachdb(zone, db);
dns_db_setloop(zone->db, zone->loop); dns_db_setloop(zone->db, zone->loop);
dns_db_setmaxrrperset(zone->db, zone->maxrrperset);
dns_db_setmaxtypepername(zone->db, zone->maxtypepername);
DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY); DNS_ZONE_SETFLAG(zone, DNS_ZONEFLG_LOADED | DNS_ZONEFLG_NEEDNOTIFY);
return (ISC_R_SUCCESS); return (ISC_R_SUCCESS);
@ -22470,7 +22498,11 @@ failure:
* Something went wrong; try again in ten minutes or * Something went wrong; try again in ten minutes or
* after a key refresh interval, whichever is shorter. * after a key refresh interval, whichever is shorter.
*/ */
dnssec_log(zone, ISC_LOG_DEBUG(3), int loglevel = ISC_LOG_DEBUG(3);
if (result != DNS_R_NOTLOADED) {
loglevel = ISC_LOG_ERROR;
}
dnssec_log(zone, loglevel,
"zone_rekey failure: %s (retry in %u seconds)", "zone_rekey failure: %s (retry in %u seconds)",
isc_result_totext(result), isc_result_totext(result),
ISC_MIN(zone->refreshkeyinterval, 600)); ISC_MIN(zone->refreshkeyinterval, 600));
@ -24153,6 +24185,8 @@ dns_zone_makedb(dns_zone_t *zone, dns_db_t **dbp) {
} }
dns_db_setloop(db, zone->loop); dns_db_setloop(db, zone->loop);
dns_db_setmaxrrperset(db, zone->maxrrperset);
dns_db_setmaxtypepername(db, zone->maxtypepername);
*dbp = db; *dbp = db;

View File

@ -2372,6 +2372,12 @@ static cfg_clausedef_t zone_clauses[] = {
{ "max-records", &cfg_type_uint32, { "max-records", &cfg_type_uint32,
CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT }, CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
{ "max-records-per-type", &cfg_type_uint32,
CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
{ "max-types-per-name", &cfg_type_uint32,
CFG_ZONE_PRIMARY | CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR |
CFG_ZONE_STUB | CFG_ZONE_STATICSTUB | CFG_ZONE_REDIRECT },
{ "max-refresh-time", &cfg_type_uint32, { "max-refresh-time", &cfg_type_uint32,
CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB }, CFG_ZONE_SECONDARY | CFG_ZONE_MIRROR | CFG_ZONE_STUB },
{ "max-retry-time", &cfg_type_uint32, { "max-retry-time", &cfg_type_uint32,

View File

@ -3160,9 +3160,18 @@ update_action(void *arg) {
dns_diff_clear(&ctx.add_diff); dns_diff_clear(&ctx.add_diff);
goto failure; goto failure;
} }
CHECK(update_one_rr(db, ver, &diff, result = update_one_rr(
DNS_DIFFOP_ADD, db, ver, &diff, DNS_DIFFOP_ADD,
name, ttl, &rdata)); name, ttl, &rdata);
if (result != ISC_R_SUCCESS) {
update_log(client, zone,
LOGLEVEL_PROTOCOL,
"adding an RR "
"failed: %s",
isc_result_totext(
result));
goto failure;
}
} }
} }
} else if (update_class == dns_rdataclass_any) { } else if (update_class == dns_rdataclass_any) {