chg: usr: Parent-centric resolver

The `named` resolver now uses a separate "delegation database" to store zone referral data instead of the DNS cache. This new database holds the NS RRset on the parent side of a zone cut, as well as necessary glue records that were included in the referral. The NS RRset from the child side is cached in the DNS cache and is not used for name resolution.

This will be a step toward simplifying resolver logic and also supporting DELEG referrals.

Closes #3311

Merge branch 'colin/deleg-resolver' into 'main'

See merge request isc-projects/bind9!11621
This commit is contained in:
Colin Vidal 2026-03-30 21:45:03 +02:00
commit e3b60291aa
78 changed files with 4028 additions and 843 deletions

View file

@ -53,6 +53,7 @@
#include <dns/byaddr.h>
#include <dns/cache.h>
#include <dns/client.h>
#include <dns/deleg.h>
#include <dns/dispatch.h>
#include <dns/fixedname.h>
#include <dns/keytable.h>
@ -90,6 +91,11 @@
#define MAX_TOTAL 200
#define MAX_RESTARTS 11
/*
* Also see max-delegation-servers default setting (bin/include/defaultconfig.h0
*/
#define DEFAULT_MAX_DELEGATION_SERVERS 13
/* Variables used internally by delv. */
static dns_view_t *view = NULL;
static ns_server_t *sctx = NULL;
@ -2154,6 +2160,8 @@ run_server(void *arg) {
dns_view_setdstport(view, destport);
dns_view_setmaxrestarts(view, restarts);
dns_view_setmaxqueries(view, maxtotal);
dns_view_setmaxdelegationservers(view, DEFAULT_MAX_DELEGATION_SERVERS);
dns_delegdb_create(&view->deleg);
CHECK(dns_rootns_create(isc_g_mctx, dns_rdataclass_in, hintfile,
&roothints));

View file

@ -244,6 +244,7 @@ struct dumpcontext {
isc_mem_t *mctx;
bool dumpcache;
bool dumpzones;
bool dumpdeleg;
bool dumpadb;
bool dumpexpired;
bool dumpfail;
@ -3892,6 +3893,13 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
}
/*
* Since both the delegation DB and ADB uses 1/8 of the
* `max_cache_size`, let's use 6/8 for the main cache DB.
*/
const size_t cache_size_slice = max_cache_size / 8;
const size_t main_cache_size = cache_size_slice * 6;
/* Check-names. */
obj = NULL;
result = named_checknames_get(maps, response_synonyms, &obj);
@ -4128,6 +4136,12 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
INSIST(result == ISC_R_SUCCESS);
stale_refresh_time = cfg_obj_asduration(obj);
result = dns_viewlist_find(&named_g_server->viewlist, view->name,
view->rdclass, &pview);
if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) {
goto cleanup;
}
/*
* Configure the view's cache.
*
@ -4171,7 +4185,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
if (nsc != NULL) {
if (!cache_sharable(nsc->primaryview, view, zero_no_soattl,
max_cache_size, max_stale_ttl,
main_cache_size, max_stale_ttl,
stale_refresh_time))
{
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
@ -4193,11 +4207,6 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
}
}
} else if (strcmp(cachename, view->name) == 0) {
result = dns_viewlist_find(&named_g_server->viewlist, cachename,
view->rdclass, &pview);
if (result != ISC_R_NOTFOUND && result != ISC_R_SUCCESS) {
goto cleanup;
}
if (pview != NULL) {
if (!cache_reusable(pview, view, zero_no_soattl)) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
@ -4222,7 +4231,6 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
dns_resolver_getqueryrttstats(pview->resolver,
&resqueryinrttstats,
&resqueryoutrttstats);
dns_view_detach(&pview);
}
}
@ -4253,7 +4261,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
dns_view_setcache(view, cache, shared_cache);
dns_cache_setcachesize(cache, max_cache_size);
dns_cache_setcachesize(cache, main_cache_size);
dns_cache_setservestalettl(cache, max_stale_ttl);
dns_cache_setservestalerefresh(cache, stale_refresh_time);
@ -4278,6 +4286,24 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
CHECK(dns_view_createresolver(view, resopts, tlsctx_client_cache,
dispatch4, dispatch6));
/*
* The deleg DB cache is preserved if reconfiguring/reloading the
* server.
*/
if (pview != NULL) {
dns_delegdb_reuse(pview, view);
} else {
dns_delegdb_create(&view->deleg);
}
dns_delegdb_setsize(view->deleg, cache_size_slice);
/*
* The previous view isn't needed anymore.
*/
if (pview != NULL) {
dns_view_detach(&pview);
}
if (resstats == NULL) {
isc_stats_create(mctx, &resstats, dns_resstatscounter_max);
}
@ -4304,8 +4330,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
* MAX_ADB_SIZE_FOR_CACHESHARE when the cache is shared.
*/
max_adb_size = 0;
if (max_cache_size != 0U) {
max_adb_size = max_cache_size / 8;
if (cache_size_slice != 0U) {
max_adb_size = cache_size_slice;
if (max_adb_size == 0U) {
max_adb_size = 1; /* Force minimum. */
}
@ -6542,7 +6568,7 @@ tat_send(void *arg) {
char namebuf[DNS_NAME_FORMATSIZE];
dns_fixedname_t fdomain;
dns_name_t *domain = NULL;
dns_rdataset_t nameservers;
dns_delegset_t *delegset = NULL;
isc_result_t result;
dns_name_t *keyname = NULL;
dns_name_t *tatname = NULL;
@ -6574,25 +6600,23 @@ tat_send(void *arg) {
* to.
*
* After the dns_view_findzonecut() call, 'domain' will hold the
* deepest zone cut we can find for 'keyname' while 'nameservers' will
* hold the NS RRset at that zone cut.
* deepest zone cut we can find for 'keyname' while 'delegset' will
* hold the NS names at that zone cut.
*/
domain = dns_fixedname_initname(&fdomain);
dns_rdataset_init(&nameservers);
result = dns_view_bestzonecut(tat->view, keyname, domain, NULL, 0, 0,
true, true, &nameservers);
true, true, &delegset);
if (result == ISC_R_SUCCESS) {
result = dns_resolver_createfetch(
tat->view->resolver, tatname, dns_rdatatype_null,
domain, &nameservers, NULL, NULL, 0, 0, 0, NULL, NULL,
NULL, tat->loop, tat_done, tat, NULL, &tat->rdataset,
domain, delegset, NULL, NULL, 0, 0, 0, NULL, NULL, NULL,
tat->loop, tat_done, tat, NULL, &tat->rdataset,
&tat->sigrdataset, &tat->fetch);
/*
* dns_resolver_createfetch() will create its own copy of
* nameservers.
* dns_resolver_createfetch() will internally attach delegset.
*/
dns_rdataset_cleanup(&nameservers);
dns_delegset_detach(&delegset);
}
/*
@ -10759,6 +10783,12 @@ resume:
dns_db_attach(dctx->view->view->cachedb, &dctx->cache);
}
if (dctx->dumpdeleg) {
fprintf(dctx->fp, ";\n; Delegation cache\n;\n");
dns_delegdb_dump(dctx->view->view->deleg, dctx->dumpexpired,
dctx->fp);
}
if (dctx->cache != NULL) {
if (dctx->dumpadb) {
dns_adb_t *adb = NULL;
@ -10774,6 +10804,7 @@ resume:
}
dns_db_detach(&dctx->cache);
}
if (dctx->dumpzones) {
style = &dns_master_style_full;
nextzone:
@ -10862,6 +10893,7 @@ named_server_dumpdb(named_server_t *server, isc_lex_t *lex,
.mctx = server->mctx,
.dumpcache = true,
.dumpadb = true,
.dumpdeleg = true,
.dumpfail = true,
.viewlist = ISC_LIST_INITIALIZER,
};
@ -10890,23 +10922,33 @@ named_server_dumpdb(named_server_t *server, isc_lex_t *lex,
/* only dump zones, suppress caches */
dctx->dumpadb = false;
dctx->dumpcache = false;
dctx->dumpdeleg = false;
dctx->dumpfail = false;
dctx->dumpzones = true;
ptr = next_token(lex, NULL);
} else if (ptr != NULL && strcmp(ptr, "-deleg") == 0) {
/* only dump deleg db, suppress other caches */
dctx->dumpcache = false;
dctx->dumpfail = false;
dctx->dumpadb = false;
ptr = next_token(lex, NULL);
} else if (ptr != NULL && strcmp(ptr, "-adb") == 0) {
/* only dump adb, suppress other caches */
dctx->dumpcache = false;
dctx->dumpdeleg = false;
dctx->dumpfail = false;
ptr = next_token(lex, NULL);
} else if (ptr != NULL && strcmp(ptr, "-bad") == 0) {
/* only dump badcache, suppress other caches */
dctx->dumpadb = false;
dctx->dumpdeleg = false;
dctx->dumpcache = false;
dctx->dumpfail = false;
ptr = next_token(lex, NULL);
} else if (ptr != NULL && strcmp(ptr, "-fail") == 0) {
/* only dump servfail cache, suppress other caches */
dctx->dumpadb = false;
dctx->dumpdeleg = false;
dctx->dumpcache = false;
ptr = next_token(lex, NULL);
}
@ -11207,6 +11249,13 @@ cleanup:
return result;
}
static void
flush_delegdb(dns_view_t *view) {
dns_delegdb_shutdown(view->deleg);
dns_delegdb_detach(&view->deleg);
dns_delegdb_create(&view->deleg);
}
isc_result_t
named_server_flushcache(named_server_t *server, isc_lex_t *lex) {
char *ptr = NULL;
@ -11284,6 +11333,8 @@ named_server_flushcache(named_server_t *server, isc_lex_t *lex) {
* two views. Then this will be a O(n^2/4) operation.
*/
ISC_LIST_FOREACH(server->viewlist, view, link) {
flush_delegdb(view);
if (!dns_view_iscacheshared(view)) {
continue;
}
@ -11338,11 +11389,73 @@ named_server_flushcache(named_server_t *server, isc_lex_t *lex) {
return result;
}
static bool
flushnode_cache(dns_view_t *view, const dns_name_t *name, const char *target,
bool tree) {
isc_result_t result;
/*
* It's a little inefficient to try flushing name for all views
* if some of the views share a single cache. But since the
* operation is lightweight we prefer simplicity here.
*/
result = dns_view_flushnode(view, name, tree);
if (result != ISC_R_SUCCESS) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER,
ISC_LOG_ERROR,
"flushing %s '%s' in cache view '%s' "
"failed: %s",
tree ? "tree" : "name", target, view->name,
isc_result_totext(result));
}
return result == ISC_R_SUCCESS;
}
static bool
flushnode_delegcache(dns_view_t *view, const dns_name_t *name,
const char *target, bool tree) {
isc_result_t result;
result = dns_delegdb_delete(view->deleg, name, tree);
if (result != ISC_R_SUCCESS) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER,
ISC_LOG_ERROR,
"flushing %s '%s' in delegation cache view '%s' "
"failed: %s",
tree ? "tree" : "name", target, view->name,
isc_result_totext(result));
}
return result == ISC_R_SUCCESS;
}
static void
logflushcachesuccess(const char *viewname, const char *target, bool tree,
bool deleg) {
const char *cache =
deleg ? (viewname == NULL ? "delegation cache for all views"
: "delegation cache for view")
: (viewname == NULL ? "DNS cache for all views"
: "DNS cache for view");
if (viewname == NULL) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER,
ISC_LOG_INFO, "flushing %s '%s' in %s succeeded",
tree ? "tree" : "name", target, cache);
} else {
isc_log_write(NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER,
ISC_LOG_INFO,
"flushing %s '%s' in %s %s succeeded",
tree ? "tree" : "name", target, cache, viewname);
}
}
isc_result_t
named_server_flushnode(named_server_t *server, isc_lex_t *lex, bool tree) {
char *ptr = NULL, *viewname = NULL;
char target[DNS_NAME_FORMATSIZE];
bool flushed;
bool flushedcache = false, flusheddelegcache = false;
bool found;
isc_result_t result = ISC_R_SUCCESS;
isc_buffer_t b;
@ -11371,51 +11484,41 @@ named_server_flushnode(named_server_t *server, isc_lex_t *lex, bool tree) {
viewname = next_token(lex, NULL);
isc_loopmgr_pause();
flushed = true;
found = false;
ISC_LIST_FOREACH(server->viewlist, view, link) {
if (viewname != NULL && strcasecmp(viewname, view->name) != 0) {
continue;
}
found = true;
/*
* It's a little inefficient to try flushing name for all views
* if some of the views share a single cache. But since the
* operation is lightweight we prefer simplicity here.
*/
result = dns_view_flushnode(view, name, tree);
if (result != ISC_R_SUCCESS) {
flushed = false;
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
"flushing %s '%s' in cache view '%s' "
"failed: %s",
tree ? "tree" : "name", target,
view->name, isc_result_totext(result));
if (flushnode_cache(view, name, target, tree)) {
flushedcache = true;
}
if (flushnode_delegcache(view, name, target, tree)) {
flusheddelegcache = true;
}
}
if (flushed && found) {
if (viewname != NULL) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_INFO,
"flushing %s '%s' in cache view '%s' "
"succeeded",
tree ? "tree" : "name", target, viewname);
} else {
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_INFO,
"flushing %s '%s' in all cache views "
"succeeded",
tree ? "tree" : "name", target);
}
if (flushedcache && found) {
logflushcachesuccess(viewname, target, tree, false);
result = ISC_R_SUCCESS;
} else {
}
if (flusheddelegcache && found) {
logflushcachesuccess(viewname, target, tree, true);
result = ISC_R_SUCCESS;
}
if (!flushedcache && !flusheddelegcache) {
if (!found) {
isc_log_write(NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
"flushing %s '%s' in cache view '%s' "
"failed: view not found",
tree ? "tree" : "name", target, viewname);
isc_log_write(
NAMED_LOGCATEGORY_GENERAL,
NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR,
"flushing %s '%s' in caches for view '%s' "
"failed: view not found",
tree ? "tree" : "name", target, viewname);
}
result = ISC_R_FAILURE;
}

View file

@ -239,12 +239,12 @@ Currently supported commands are:
output file is moved to ".1", and so on. If ``number`` is specified, then
the number of backup log files is limited to that number.
.. option:: dumpdb [-all | -cache | -zones | -adb | -bad | -expired | -fail] [view ...]
.. option:: dumpdb [-all | -cache | -deleg | -zones | -adb | -deleg | -bad | -expired | -fail] [view ...]
This command dumps the server's caches (default) and/or zones to the dump file for
the specified views. If no view is specified, all views are dumped.
(See the ``dump-file`` option in the BIND 9 Administrator Reference
Manual.)
This command dumps the server's caches (default) and/or zones to the
dump file for the specified views. If no view is specified, all views
are dumped. (See the ``dump-file`` option in the BIND 9 Administrator
Reference Manual.)
.. option:: fetchlimit [view]

View file

@ -373,6 +373,8 @@ fi
n=$((n + 1))
echo_i "testing NS handling in ANY responses (recursive) ($n)"
ret=0
# pre-cache the address record
$DIG $DIGOPTS -t A ns1.rt.example @10.53.0.3 >/dev/null || ret=1
$DIG $DIGOPTS -t ANY rt.example @10.53.0.3 >dig.out.$n || ret=1
grep "AUTHORITY: 0" dig.out.$n >/dev/null || ret=1
grep "NS[ ]*ns" dig.out.$n >/dev/null || ret=1

View file

@ -0,0 +1,12 @@
ns1 simulates a root server
ns2 is an auth server (over example.) which is also a resolver.
ns3 is an auth server on `sub.example.`.
The point of the test is to show that because ns2 knows the IP address of the
NS delegating sub.example., there won't be any queries to resolve
ns.sub.example.
In order to prove it, ns2 also does a DNSTAP dump of its outgoing queries and
checks there is no NS queries to ns3 (but only an AAAA query).

View file

@ -0,0 +1,30 @@
/*
* 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 {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
recursion no;
notify yes;
dnssec-validation no;
};
zone "." {
type primary;
file "root.db";
};

View file

@ -0,0 +1,24 @@
; 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 300
. IN SOA a.root.servers.nil. a.root.servers.nil. (
2000042100 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
. NS a.root-servers.nil.
a.root-servers.nil. A 10.53.0.1
example. NS ns.example.
ns.example. A 10.53.0.2

View file

@ -0,0 +1,24 @@
; 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 300
@ IN SOA example. example. (
2000042100 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
@ NS ns.example.
ns.example. A 10.53.0.2
sub.example. NS ns.sub.example.
ns.sub.example. A 10.53.0.3

View file

@ -0,0 +1,41 @@
/*
* 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 {
query-source address 10.53.0.2;
notify-source 10.53.0.2;
transfer-source 10.53.0.2;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.2; };
listen-on-v6 { none; };
recursion yes;
notify yes;
dnssec-validation no;
dnstap { resolver query; };
dnstap-output file "dnstap.out";
};
zone "." {
type primary;
file "example.db";
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};

View file

@ -0,0 +1,30 @@
/*
* 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 {
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; };
recursion no;
notify yes;
dnssec-validation no;
};
zone "." {
type primary;
file "sub.example.db";
};

View file

@ -0,0 +1,23 @@
; 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 300
@ IN SOA sub.example. sub.example. (
2000042100 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
@ NS ns.sub.example.
ns.sub.example. A 10.53.0.3
aaaa.sub.example. AAAA ac::dc

View file

@ -0,0 +1,48 @@
# 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.
import os
import isctest
import isctest.mark
pytestmark = [isctest.mark.with_dnstap]
def line_to_query(line):
# dnstap-read output line example
# 05-Feb-2026 11:00:57.853 RQ 10.53.0.6:38507 -> 10.53.0.3:22047 TCP 56b fooXXX.example./IN/NS
_, _, _, _, _, _, _, _, query = line.split(" ", 9)
return query
def extract_dnstap(ns):
ns.rndc("dnstap -roll 1")
path = os.path.join(ns.identifier, "dnstap.out.0")
dnstapread = isctest.run.cmd(
[isctest.vars.ALL["DNSTAPREAD"], path],
)
lines = dnstapread.out.splitlines()
return list(map(line_to_query, lines))
def test_auth_res_deleg(ns2):
msg = isctest.query.create("aaaa.sub.example.", "AAAA")
res = isctest.query.udp(msg, ns2.ip)
isctest.check.noerror(res)
assert len(res.answer[0]) == 1
res.answer[0].ttl = 300
assert str(res.answer[0]) == "aaaa.sub.example. 300 IN AAAA ac::dc"
queries = extract_dnstap(ns2)
assert len(queries) == 1
assert queries[0] == "aaaa.sub.example/IN/AAAA"

View file

@ -0,0 +1,23 @@
; 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 999999
bar.com. IN SOA hostmaster.nominum.com. a.root-servers.nil. (
2000042100
600
600
1200
600
)
IN NS ns
ns IN A 10.53.0.1
foo IN NS ns.somehost.com.
lame-and-expired-soon 1 IN NS ns.somehost.com.

View file

@ -2940,3 +2940,5 @@ $ORIGIN NETFLIGHT.com.
DNS IN A 207.88.32.2
$ORIGIN MERCHANTWARE.CON.
NS1 IN A 209.170.142.34
$ORIGIN somehost.com.
ns IN A 10.53.0.3

View file

@ -32,6 +32,11 @@ zone "." {
file "example.db";
};
zone "bar.com" {
type primary;
file "bar.com.db";
};
zone "flushtest.example" {
type primary;
file "flushtest.db";

View file

@ -1 +1 @@
-m record -c named.conf -d 3 -D cacheclean-ns2 -g -T maxcachesize=2097152
-m record -c named.conf -d 99 -D cacheclean-ns2 -g -T maxcachesize=2097152

View file

@ -0,0 +1,21 @@
; 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 999999
foo.bar.com. IN SOA hostmaster.nominum.com. a.root-servers.nil. (
2000042100
600
600
1200
600
)
IN NS ns.somehost.com.
gee IN A 9.9.9.9

View file

@ -0,0 +1,34 @@
/*
* 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 {
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; };
allow-transfer { any; };
recursion no;
dnssec-validation no;
notify yes;
check-integrity no;
minimal-responses no;
};
zone "foo.bar.com" {
type primary;
file "foo.bar.com.db";
};

View file

@ -242,20 +242,23 @@ status=$((status + ret))
n=$((n + 1))
echo_i "check flushtree clears adb correctly ($n)"
ret=0
load_cache
# Because the resolver is parent centric, no NS will be used from ADB using
# flushtest.example, as the NS is in-domain. So force an NS lookup using
# foo.bar.com domain.
$DIG @10.53.0.2 -p ${PORT} A gee.foo.bar.com >/dev/null
dump_cache
mv ns2/named_dump.db.test$n ns2/named_dump.db.test$n.a
sed -n '/plain success\/timeout/,/Unassociated entries/p' \
ns2/named_dump.db.test$n.a >sed.out.$n.a
grep 'plain success/timeout' sed.out.$n.a >/dev/null 2>&1 || ret=1
grep 'ns.flushtest.example' sed.out.$n.a >/dev/null 2>&1 || ret=1
$RNDC $RNDCOPTS flushtree flushtest.example || ret=1
grep 'ns.somehost.com.' sed.out.$n.a >/dev/null 2>&1 || ret=1
$RNDC $RNDCOPTS flushtree com. || ret=1
dump_cache
mv ns2/named_dump.db.test$n ns2/named_dump.db.test$n.b
sed -n '/plain success\/timeout/,/Unassociated entries/p' \
ns2/named_dump.db.test$n.b >sed.out.$n.b
grep 'plain success/timeout' sed.out.$n.b >/dev/null 2>&1 || ret=1
grep 'ns.flushtest.example' sed.out.$n.b >/dev/null 2>&1 && ret=1
grep 'ns.somehost.com.' sed.out.$n.b >/dev/null 2>&1 && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -0,0 +1,116 @@
# 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.
from re import compile as Re
import time
import isctest
DUMP_FILE_NAME = "ns2/named_dump.db"
def warmup_cache(ns):
# Let's flush all caches first so we're sure the query will fully run into the resolver.
flush_caches(ns, "flush", "", "flushing caches in all views succeeded")
msg = isctest.query.create("gee.foo.bar.com.", "A")
res = isctest.query.udp(msg, ns.ip)
isctest.check.noerror(res)
msg = isctest.query.create("whatever.lame-and-expired-soon.bar.com.", "A")
# This will fail, we don't care, this is just to have the entry in the cache
isctest.query.udp(msg, ns.ip)
assert len(res.answer) == 1
def check_cache_expired(ns):
time.sleep(2)
with ns.watch_log_from_here() as watcher:
ns.rndc("dumpdb -expired")
watcher.wait_for_line("dumpdb complete")
with isctest.log.WatchLogFromStart(DUMP_FILE_NAME) as watcher:
patterns = [
Re("lame-and-expired-soon.bar.com. 0 DELEG server-name=ns.somehost.com."),
Re("foo.bar.com. 99999[5-9] DELEG server-name=ns.somehost.com."),
]
watcher.wait_for_all(patterns)
def check_cache(ns, hit):
with ns.watch_log_from_here() as watcher:
ns.rndc("dumpdb -deleg")
watcher.wait_for_line("dumpdb complete")
with isctest.log.WatchLogFromStart(DUMP_FILE_NAME) as watcher:
if hit:
pattern = Re("foo.bar.com. 99999[5-9] DELEG server-name=ns.somehost.com.")
watcher.wait_for_line(pattern)
else:
seq = ["; Delegation cache", ";", ";", "; Start view _bind"]
watcher.wait_for_sequence(seq)
def reload_server(ns):
with ns.watch_log_from_here() as watcher:
ns.rndc("reload")
watcher.wait_for_line("running")
with ns.watch_log_from_here() as watcher:
ns.rndc("reconfig")
watcher.wait_for_line("running")
def flush_caches(ns, flushcmd, flusharg, confirm):
with ns.watch_log_from_here() as watcher:
ns.rndc(f"{flushcmd} {flusharg}")
watcher.wait_for_line(confirm)
def test_cacheclean_deleg(ns2):
# Make sure the delegation cache has foo.bar.com.
warmup_cache(ns2)
# Flushing the cache
check_cache(ns2, True)
# Reloading the server keeps the cache hot
reload_server(ns2)
# The cache is still hot
check_cache(ns2, True)
# Flush the cache, and its now cold
flush_caches(ns2, "flush", "", "flushing caches in all views succeeded")
check_cache(ns2, False)
# Flush just the name foo.bar.com.
warmup_cache(ns2)
check_cache(ns2, True)
flush_caches(
ns2,
"flushname",
"foo.bar.com.",
"flushing name 'foo.bar.com.' in delegation cache for all views succeeded",
)
check_cache(ns2, False)
# Flush the whole .com. tree (need to flush
warmup_cache(ns2)
check_cache(ns2, True)
flush_caches(
ns2,
"flushtree",
"com.",
"flushing tree 'com.' in DNS cache for all views succeeded",
)
check_cache(ns2, False)
# Check -expired
warmup_cache(ns2)
check_cache(ns2, True)
check_cache_expired(ns2)

View file

@ -26,7 +26,7 @@ options {
max-recursion-queries 50;
max-query-restarts 50;
max-query-count 100;
max-query-count 50;
};
key rndc_key {

View file

@ -34,6 +34,8 @@ ns3.example.dname. A 10.53.0.3
jeff.dname. NS ns.jeff.dname.
ns.jeff.dname. A 10.53.0.3
mutt.dname. NS ns.jeff.dname.
domain0.nil. NS ns2.domain0.nil
domain1.nil. NS ns2.domain0.nil
domain2.nil. NS ns2.domain0.nil

View file

@ -647,7 +647,7 @@ echo_i "checking handling of illegal NS below DNAME ($n)"
ret=0
$DIG $DIGOPTS @10.53.0.7 DNAME jeff.dname. >dig.out.ns7.1.$n 2>&1
grep 'status: NOERROR' dig.out.ns7.1.$n >/dev/null 2>&1 || ret=1
$DIG $DIGOPTS @10.53.0.7 NS jeff.dname. >dig.out.ns7.2.$n 2>&1
$DIG $DIGOPTS @10.53.0.7 NS mutt.dname. >dig.out.ns7.2.$n 2>&1
grep 'status: SERVFAIL' dig.out.ns7.2.$n >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -566,7 +566,7 @@ sys.exit(1)'; then
ret=0
pat='10\.53\.0\.10 .*\[cookie=................................\] \[ttl'
# prime EDNS COOKIE state
$DIG $DIGOPTS @10.53.0.1 tsig. >dig.out.test$n.1 || ret=1
$DIG $DIGOPTS @10.53.0.1 NS tsig. >dig.out.test$n.1 || ret=1
grep "status: NOERROR" dig.out.test$n.1 >/dev/null || ret=1
rndc_dumpdb ns1
# prime cache with NS response for QNAME minimisation

View file

@ -25,7 +25,7 @@ options {
listen-on-v6 { none; };
allow-transfer { any; };
minimal-any no;
minimal-responses no;
minimal-responses yes;
recursion no;
notify yes;
dnssec-validation yes;

View file

@ -88,21 +88,6 @@ def test_insecure_rrsig():
assert res.authority[0].rdtype == rdatatype.SOA
def test_insecure_glue():
# check that for a query against a validating resolver where the
# authoritative zone is unsigned (insecure delegation), glue is returned
# in the additional section
msg = isctest.query.create("a.insecure.example", "A")
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.authority, 1)
isctest.check.rr_count_eq(res.additional, 1)
assert str(res.additional[0].name) == "ns3.insecure.example."
addrs = [str(a) for a in res.additional[0]]
assert "10.53.0.3" in addrs
def test_adflag():
# compare auth and recursive answers
msg = isctest.query.create("a.example", "A", dnssec=False)
@ -185,7 +170,7 @@ def test_positive_validation_nsec3():
isctest.check.same_answer(res1, res2)
isctest.check.noerror(res2)
isctest.check.adflag(res2)
isctest.check.rr_count_eq(res2.authority, 4)
isctest.check.rr_count_eq(res2.authority, 2)
# unknown NSEC3 hash algorithm
msg = isctest.query.create("nsec3-unknown.example", "SOA", dnssec=False)
@ -514,10 +499,10 @@ def test_excessive_nsec3_iterations(ns2, ns3):
msg = isctest.query.create("wild.a.too-many-iterations", "A")
res1 = isctest.query.tcp(msg, "10.53.0.2")
res2 = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.same_data(res1, res2)
isctest.check.section_equal(res1.answer, res2.answer)
isctest.check.noadflag(res2)
isctest.check.rr_count_eq(res2.answer, 2)
isctest.check.rr_count_eq(res2.authority, 4)
isctest.check.rr_count_eq(res2.authority, 2)
a, _ = res2.answer
assert str(a.name) == "wild.a.too-many-iterations."
assert str(a[0]) == "10.0.0.3"
@ -1005,14 +990,14 @@ def test_validation_recovery(ns2, ns4):
msg = isctest.query.create("inconsistent", "NS", dnssec=False, cd=True)
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.noadflag(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.additional, 1)
isctest.check.rr_count_eq(res.answer, 2)
isctest.check.rr_count_eq(res.additional, 0)
msg = isctest.query.create("inconsistent", "NS", cd=True)
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.noadflag(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.additional, 1)
isctest.check.rr_count_eq(res.answer, 3)
isctest.check.rr_count_eq(res.additional, 0)
msg = isctest.query.create("inconsistent", "NS")
res = isctest.query.tcp(msg, "10.53.0.4")
@ -1073,7 +1058,7 @@ def test_validating_forwarder(ns4, ns9):
msg = isctest.query.create("inconsistent", "NS", dnssec=False, cd=True)
res = isctest.query.tcp(msg, "10.53.0.9")
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.answer, 2)
isctest.check.rr_count_eq(res.additional, 0)
isctest.check.noadflag(res)
@ -1081,7 +1066,7 @@ def test_validating_forwarder(ns4, ns9):
res = isctest.query.tcp(msg, "10.53.0.9")
isctest.check.rr_count_eq(res.additional, 0)
isctest.check.noadflag(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.answer, 3)
isctest.check.rr_count_eq(res.authority, 0)
isctest.check.rr_count_eq(res.additional, 0)
@ -1269,12 +1254,13 @@ def test_pending_ds(ns4):
msg = isctest.query.create("insecure.example", "DS", cd=True)
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 0)
isctest.check.rr_count_eq(res.authority, 4)
msg = isctest.query.create("a.insecure.example", "A")
res = isctest.query.tcp(msg, "10.53.0.4")
isctest.check.noerror(res)
isctest.check.rr_count_eq(res.answer, 1)
isctest.check.rr_count_eq(res.authority, 1)
isctest.check.rr_count_eq(res.authority, 0)
isctest.check.noadflag(res)

View file

@ -10,8 +10,7 @@
# information regarding copyright ownership.
from dns.edns import EDECode
# from dns.edns import EDECode
import pytest
from isctest.util import param
@ -74,7 +73,9 @@ def test_trust_anchors():
res1 = isctest.query.tcp(msg, "10.53.0.3")
res2 = isctest.query.tcp(msg, "10.53.0.5")
isctest.check.noerror(res1)
isctest.check.ede(res2, EDECode.UNSUPPORTED_DNSKEY_ALGORITHM)
# This EDE code should be added by the validator, but currently it isn't.
# See issue #5832
# isctest.check.ede(res2, EDECode.UNSUPPORTED_DNSKEY_ALGORITHM)
isctest.check.noerror(res2)
isctest.check.noadflag(res2)
@ -82,7 +83,10 @@ def test_trust_anchors():
res1 = isctest.query.tcp(msg, "10.53.0.3")
res2 = isctest.query.tcp(msg, "10.53.0.5")
isctest.check.noerror(res1)
isctest.check.ede(res2, EDECode.UNSUPPORTED_DNSKEY_ALGORITHM)
# This EDE code should be added by the validator, but currently it isn't.
# See issue #5832
# isctest.check.ede(res2, EDECode.UNSUPPORTED_DNSKEY_ALGORITHM)
print(res2)
isctest.check.noerror(res2)
isctest.check.noadflag(res2)

View file

@ -491,7 +491,7 @@ ret=0
hex=$($DNSTAPREAD -x ns3/dnstap.out | tail -1)
echo $hex | $WIRETEST >dnstap.hex
grep 'status: NOERROR' dnstap.hex >/dev/null 2>&1 || ret=1
grep 'ANSWER: 3, AUTHORITY: 1' dnstap.hex >/dev/null 2>&1 || ret=1
grep 'ANSWER: 3, AUTHORITY: 0' dnstap.hex >/dev/null 2>&1 || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -125,20 +125,6 @@ closeversion(dns_db_t *db, dns_dbversion_t **versionp,
dns__db_closeversion(sampledb->db, versionp, commit DNS__DB_FLARG_PASS);
}
static isc_result_t
findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
isc_stdtime_t now, dns_dbnode_t **nodep, dns_name_t *foundname,
dns_name_t *dcname, dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
sampledb_t *sampledb = (sampledb_t *)db;
REQUIRE(VALID_SAMPLEDB(sampledb));
return dns__db_findzonecut(sampledb->db, name, options, now, nodep,
foundname, dcname, rdataset,
sigrdataset DNS__DB_FLARG_PASS);
}
static isc_result_t
createiterator(dns_db_t *db, unsigned int options,
dns_dbiterator_t **iteratorp) {
@ -363,7 +349,6 @@ static dns_dbmethods_t sampledb_methods = {
.newversion = newversion,
.attachversion = attachversion,
.closeversion = closeversion,
.findzonecut = findzonecut,
.createiterator = createiterator,
.findrdataset = findrdataset,
.allrdatasets = allrdatasets,

View file

@ -167,7 +167,8 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
cp ns3/named2.conf ns3/named.conf
rndc_reconfig ns3 10.53.0.3
stop_server --use-rndc --port ${CONTROLPORT} ns3
start_server --noclean --restart --port ${PORT} ns3
n=$((n + 1))
echo_i "checking lame server clients are dropped at the per-domain limit ($n)"
@ -177,14 +178,17 @@ success=0
sendcmd 10.53.0.4 send-responses "disable"
for try in 1 2 3 4 5; do
burst 10.53.0.3 d $try 300
$DIGCMD a ${try}.example >dig.out.ns3.$n.$try
$DIGCMD a ${try}.example >dig.out.ns3.$n.$try || true
grep "status: NOERROR" dig.out.ns3.$n.$try >/dev/null 2>&1 \
&& success=$((success + 1))
grep "status: SERVFAIL" dig.out.ns3.$n.$try >/dev/null 2>&1 \
&& fail=$(($fail + 1))
stat 10.53.0.3 40 40 || ret=1
allowed=$(rndccmd 10.53.0.3 fetchlimit | awk '/lamesub/ { print $6 }')
[ "${allowed:-0}" -eq 40 ] || ret=1
[ "${allowed:-0}" -eq 40 ] || {
echo_i "allowed ${allowed}/40"
ret=1
}
[ $ret -eq 1 ] && break
sleep 1
done
@ -202,10 +206,9 @@ for try in 1 2 3 4 5; do
sleep 1
done
zspill=$(grep 'spilled due to zone' ns3/named.stats | sed 's/\([0-9][0-9]*\) spilled.*/\1/')
[ -z "$zspill" ] && zspill=0
[ "${zspill:-0}" -ne 0 ] || ret=1
drops=$(grep 'queries dropped' ns3/named.stats | sed 's/\([0-9][0-9]*\) queries.*/\1/')
[ -z "$drops" ] && drops=0
[ "$drops" -ge "$zspill" ] || ret=1
[ "${drops:-0}" -ne 0 ] || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))

View file

@ -200,18 +200,25 @@ def check_filter(addr, altaddr, ftype, break_dnssec, recursive):
isctest.log.debug(
f"check that {qtype} is omitted from additional section, qtype=MX, unsigned"
)
check_additional(addr, addr, "unsigned", "mx", ftype, False, 2)
if recursive:
check_additional(addr, addr, "unsigned", "mx", ftype, False, 1)
else:
check_additional(addr, addr, "unsigned", "mx", ftype, False, 2)
isctest.log.debug(
f"check that {qtype} is included in additional section, qtype=MX, signed, unless break-dnssec is enabled"
)
if break_dnssec:
if recursive and break_dnssec:
check_additional(addr, addr, "signed", "mx", ftype, False, 2)
elif recursive:
check_additional(addr, addr, "signed", "mx", ftype, True, 4)
elif break_dnssec:
check_additional(addr, addr, "signed", "mx", ftype, False, 4)
else:
check_additional(addr, addr, "signed", "mx", ftype, True, 8)
def check_filter_other_family(addr, ftype):
def check_filter_other_family(addr, ftype, recursive):
isctest.log.debug(
"check that the filtered type is returned when both AAAA and A record exists, unsigned, over other family"
)
@ -220,4 +227,7 @@ def check_filter_other_family(addr, ftype):
isctest.log.debug(
"check that the filtered type is included in additional section, qtype=MX, unsigned, over other family"
)
check_additional(addr, addr, "unsigned", "mx", ftype, True, 4)
if recursive:
check_additional(addr, addr, "unsigned", "mx", ftype, True, 2)
else:
check_additional(addr, addr, "unsigned", "mx", ftype, True, 4)

View file

@ -51,13 +51,13 @@ def test_filter_a_on_v4(addr, altaddr, break_dnssec, recursive):
@isctest.mark.with_ipv6
@pytest.mark.parametrize(
"addr",
"addr, recursive",
[
pytest.param("fd92:7065:b8e:ffff::1", id="auth"),
pytest.param("fd92:7065:b8e:ffff::4", id="auth-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::2", id="recurs"),
pytest.param("fd92:7065:b8e:ffff::3", id="recurs-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::1", False, id="auth"),
pytest.param("fd92:7065:b8e:ffff::4", False, id="auth-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::2", True, id="recurs"),
pytest.param("fd92:7065:b8e:ffff::3", True, id="recurs-break-dnssec"),
],
)
def test_filter_a_on_v4_via_v6(addr):
check_filter_other_family(addr, "a")
def test_filter_a_on_v4_via_v6(addr, recursive):
check_filter_other_family(addr, "a", recursive)

View file

@ -71,13 +71,13 @@ def test_filter_a_on_v6(addr, altaddr, break_dnssec, recursive):
@pytest.mark.parametrize(
"addr",
"addr, recursive",
[
pytest.param("10.53.0.1", id="auth"),
pytest.param("10.53.0.4", id="auth-break-dnssec"),
pytest.param("10.53.0.2", id="recurs"),
pytest.param("10.53.0.3", id="recurs-break-dnssec"),
pytest.param("10.53.0.1", False, id="auth"),
pytest.param("10.53.0.4", False, id="auth-break-dnssec"),
pytest.param("10.53.0.2", True, id="recurs"),
pytest.param("10.53.0.3", True, id="recurs-break-dnssec"),
],
)
def test_filter_a_on_v6_via_v4(addr):
check_filter_other_family(addr, "a")
def test_filter_a_on_v6_via_v4(addr, recursive):
check_filter_other_family(addr, "a", recursive)

View file

@ -51,13 +51,13 @@ def test_filter_aaaa_on_v4(addr, altaddr, break_dnssec, recursive):
@isctest.mark.with_ipv6
@pytest.mark.parametrize(
"addr",
"addr, recursive",
[
pytest.param("fd92:7065:b8e:ffff::1", id="auth"),
pytest.param("fd92:7065:b8e:ffff::4", id="auth-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::2", id="recurs"),
pytest.param("fd92:7065:b8e:ffff::3", id="recurs-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::1", False, id="auth"),
pytest.param("fd92:7065:b8e:ffff::4", False, id="auth-break-dnssec"),
pytest.param("fd92:7065:b8e:ffff::2", True, id="recurs"),
pytest.param("fd92:7065:b8e:ffff::3", True, id="recurs-break-dnssec"),
],
)
def test_filter_aaaa_on_v4_via_v6(addr):
check_filter_other_family(addr, "aaaa")
def test_filter_aaaa_on_v4_via_v6(addr, recursive):
check_filter_other_family(addr, "aaaa", recursive)

View file

@ -72,13 +72,13 @@ def test_filter_aaaa_on_v6(addr, altaddr, break_dnssec, recursive):
@isctest.mark.with_ipv6
@pytest.mark.parametrize(
"addr",
"addr, recursive",
[
pytest.param("10.53.0.1", id="auth"),
pytest.param("10.53.0.4", id="auth-break-dnssec"),
pytest.param("10.53.0.2", id="recurs"),
pytest.param("10.53.0.3", id="recurs-break-dnssec"),
pytest.param("10.53.0.1", False, id="auth"),
pytest.param("10.53.0.4", False, id="auth-break-dnssec"),
pytest.param("10.53.0.2", True, id="recurs"),
pytest.param("10.53.0.3", True, id="recurs-break-dnssec"),
],
)
def test_filter_aaaa_on_v6_via_v4(addr):
check_filter_other_family(addr, "aaaa")
def test_filter_aaaa_on_v6_via_v4(addr, recursive):
check_filter_other_family(addr, "aaaa", recursive)

View file

@ -133,6 +133,7 @@ def create(
qtype,
qclass=dns.rdataclass.IN,
dnssec: bool = True,
rd: bool = True,
cd: bool = False,
ad: bool = True,
) -> dns.message.Message:
@ -140,7 +141,9 @@ def create(
msg = dns.message.make_query(
qname, qtype, qclass, use_edns=True, want_dnssec=dnssec
)
msg.flags = dns.flags.RD
msg.flags = 0
if rd:
msg.flags = dns.flags.RD
if ad:
msg.flags |= dns.flags.AD
if cd:

View file

@ -0,0 +1,15 @@
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.
ns1 is the root
ns2 is auth only
ns3 is resolver only
ns4 is both auth and resolver

View file

@ -0,0 +1,121 @@
# 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.
# pylint: disable=global-statement
from re import compile as Re
from dns.rcode import NOERROR, REFUSED
import isctest
EXAMPLE2_NS = "example2. 300 IN NS ns.example2."
AEXAMPLE2_A = "a.example2. 300 IN A 10.53.0.20"
NSEXAMPLE2_A = "ns.example2. 300 IN A 10.53.0.2"
EXAMPLE4_NS = "example4. 300 IN NS ns.example4."
AEXAMPLE4_A = "a.example4. 300 IN A 10.53.0.40"
NSEXAMPLE4_A = "ns.example4. 300 IN A 10.53.0.4"
AROOTSERVER_NS = ". 999999 IN NS a.root-servers.nil."
INPUTPARAMS = "ns, qname, qtype, rd, cached, rcode, answer, authority, additional"
# `minimal-responses yes` and `minimal-responses no-auth` behaves the same,
# hence they share the same input.
# The only case AUTHORITY and ADDITIONAL are provided are when strictly needed:
# - either from an authoritative server for delegation
# (cases with ns1, both AUTHORITY and glues in ADDITIONAL);
# - either from a resolver with RD=0 and no cache,
# so the resolver can only return the root hints (AUTHORITY only).
INPUTS_YES_NOAUTH = [
("ns1", "a.example2", "A", True, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", True, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
("ns2", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns2", "a.example4", "A", True, False, REFUSED, None, None, None),
("ns3", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, False, NOERROR, AEXAMPLE4_A, None, None),
("ns3", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, True, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example4", "A", True, False, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example4", "A", True, True, NOERROR, AEXAMPLE4_A, None, None),
("ns1", "a.example2", "A", False, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", False, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
("ns2", "a.example2", "A", False, False, NOERROR, AEXAMPLE2_A, None, None),
("ns2", "a.example4", "A", False, False, REFUSED, None, None, None),
("ns3", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example4", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", False, True, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns4", "a.example4", "A", False, False, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example4", "A", False, True, NOERROR, AEXAMPLE4_A, None, None),
# Resolver always provides glues with associated NS for qtype=NS
("ns3", "example2", "NS", True, False, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "example2", "NS", False, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", True, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
]
TESTSERVERS = None
FLUSHED_PATTERN = Re("flushing cache.*succeeded")
def check(ns, qname, qtype, rd, cached, rcode, answer, authority, additional):
ns = TESTSERVERS[ns]
msg = isctest.query.create(qname, qtype, rd=rd)
if cached:
cachingmsg = isctest.query.create(qname, qtype, rd=True)
isctest.query.udp(cachingmsg, ns.ip)
else:
with ns.watch_log_from_here() as watcher:
ns.rndc("flush")
watcher.wait_for_line(FLUSHED_PATTERN)
res = isctest.query.udp(msg, ns.ip)
isctest.check.rcode(res, rcode)
if answer:
assert len(res.answer) == 1
# Clamp the answer TTL to 300 to match the expected answer
# (In case the server would be a bit slow to process the
# query, and we would end up with a TTL of ~299)
res.answer[0].ttl = 300
assert str(res.answer[0]) == answer
else:
assert len(res.answer) == 0
if authority:
assert len(res.authority) == 1
assert str(res.authority[0]) == authority
else:
assert len(res.authority) == 0
if additional:
assert len(res.additional) == 1
# Clamp the answer TTL to 300 to match the expected answer
# (In case the server would be a bit slow to process the
# query, and we would end up with a TTL of ~299)
res.additional[0].ttl = 300
assert str(res.additional[0]) == additional
else:
assert len(res.additional) == 0
def reconfig(servers, templates, minresp):
global TESTSERVERS
for server in servers:
ns = servers[server]
with ns.watch_log_from_here() as watcher:
templates.render(f"{server}/named.conf", {"minresp": minresp})
ns.rndc("reload")
watcher.wait_for_line("running")
TESTSERVERS = servers

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
{% set minresp = minresp | default("yes") %}
options {
query-source address 10.53.0.1;
notify-source 10.53.0.1;
transfer-source 10.53.0.1;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.1; };
listen-on-v6 { none; };
recursion no;
notify yes;
minimal-responses @minresp@;
};
zone "." {
type primary;
file "root.db";
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};

View file

@ -0,0 +1,27 @@
; 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 300
. IN SOA gson.nominum.com. a.root.servers.nil. (
2000042100 ; serial
600 ; refresh
600 ; retry
1200 ; expire
600 ; minimum
)
. NS a.root-servers.nil.
a.root-servers.nil. A 10.53.0.1
example2. NS ns.example2.
ns.example2. A 10.53.0.2
example4. NS ns.example4.
ns.example4. A 10.53.0.4

View file

@ -0,0 +1,23 @@
; 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 300 ; 5 minutes
@ IN SOA mname1. . (
2000042407 ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
NS ns
ns A 10.53.0.2
a A 10.53.0.20

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
{% set minresp = minresp | default("yes") %}
options {
query-source address 10.53.0.2;
notify-source 10.53.0.2;
transfer-source 10.53.0.2;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.2; };
listen-on-v6 { none; };
recursion no;
notify yes;
dnssec-validation no;
minimal-responses @minresp@;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "example2" {
type primary;
file "example2.db";
};

View file

@ -0,0 +1,44 @@
/*
* 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.
*/
{% set minresp = minresp | default("yes") %}
options {
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; };
recursion yes;
notify yes;
dnssec-validation no;
minimal-responses @minresp@;
};
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 "../../_common/root.hint";
};

View file

@ -0,0 +1,23 @@
; 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 300 ; 5 minutes
@ IN SOA mname1. . (
2000042407 ; serial
20 ; refresh (20 seconds)
20 ; retry (20 seconds)
1814400 ; expire (3 weeks)
3600 ; minimum (1 hour)
)
NS ns
ns A 10.53.0.4
a A 10.53.0.40

View file

@ -0,0 +1,47 @@
/*
* 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.
*/
{% set minresp = minresp | default("yes") %}
options {
query-source address 10.53.0.4;
notify-source 10.53.0.4;
transfer-source 10.53.0.4;
port @PORT@;
pid-file "named.pid";
listen-on { 10.53.0.4; };
listen-on-v6 { none; };
recursion yes;
notify yes;
dnssec-validation no;
minimal-responses @minresp@;
};
key rndc_key {
secret "1234abcd8765";
algorithm @DEFAULT_HMAC@;
};
controls {
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
};
zone "." {
type hint;
file "../../_common/root.hint";
};
zone "example4" {
type primary;
file "example4.db";
};

View file

@ -0,0 +1,139 @@
# 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.
from dns.rcode import NOERROR, REFUSED
import pytest
from minimalresponses.common import (
AEXAMPLE2_A,
AEXAMPLE4_A,
AROOTSERVER_NS,
EXAMPLE2_NS,
EXAMPLE4_NS,
INPUTPARAMS,
NSEXAMPLE2_A,
NSEXAMPLE4_A,
check,
reconfig,
)
INPUTS = [
# ns1 provides AUTHORITY and ADDITIONAL as it delegate those zone.
("ns1", "a.example2", "A", True, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", True, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
("ns1", "a.example2", "A", False, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", False, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
# ns2 is authoritative on example2, so it gives us everything it can in any cases.
(
"ns2",
"a.example2",
"A",
True,
False,
NOERROR,
AEXAMPLE2_A,
EXAMPLE2_NS,
NSEXAMPLE2_A,
),
(
"ns2",
"a.example2",
"A",
False,
False,
NOERROR,
AEXAMPLE2_A,
EXAMPLE2_NS,
NSEXAMPLE2_A,
),
("ns2", "a.example4", "A", True, False, REFUSED, None, None, None),
("ns2", "a.example4", "A", False, False, REFUSED, None, None, None),
# ns3 being resolver only and having the answer, no AUTHORITY or ADDITIONAL are added.
# However, with RD=0 and no cache, it can't answer, so the root hints are provided
# in AUTHORITY.
("ns3", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, False, NOERROR, AEXAMPLE4_A, None, None),
("ns3", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, True, NOERROR, AEXAMPLE4_A, None, None),
("ns3", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example4", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", False, True, NOERROR, AEXAMPLE4_A, None, None),
# ns4 is authoritative on example4, so gives us everything in any cases.
# For example2, because it has the answer, it only provide the ANSWER section
# (except with no cache and RD=0, where we got the root hint, because it can't do anything else)
("ns4", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
(
"ns4",
"a.example4",
"A",
True,
True,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
("ns4", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
(
"ns4",
"a.example4",
"A",
True,
False,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
("ns4", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
(
"ns4",
"a.example4",
"A",
False,
False,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
(
"ns4",
"a.example4",
"A",
False,
True,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
# Resolver always provides glues with associated NS for qtype=NS
("ns3", "example2", "NS", True, False, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "example2", "NS", False, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", True, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
]
@pytest.fixture(scope="module", autouse=True)
def authsection_init(servers, templates):
reconfig(servers, templates, "no")
@pytest.mark.parametrize(INPUTPARAMS, INPUTS)
def test_minimalresponses_no(
ns, qname, qtype, rd, cached, rcode, answer, authority, additional
):
check(ns, qname, qtype, rd, cached, rcode, answer, authority, additional)

View file

@ -0,0 +1,26 @@
# 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.
import pytest
from minimalresponses.common import INPUTPARAMS, INPUTS_YES_NOAUTH, check, reconfig
@pytest.fixture(scope="module", autouse=True)
def authsection_init(servers, templates):
reconfig(servers, templates, "no-auth")
@pytest.mark.parametrize(INPUTPARAMS, INPUTS_YES_NOAUTH)
def test_minimalresponses_noauth(
ns, qname, qtype, rd, cached, rcode, answer, authority, additional
):
check(ns, qname, qtype, rd, cached, rcode, answer, authority, additional)

View file

@ -0,0 +1,111 @@
# 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.
from dns.rcode import NOERROR, REFUSED
import pytest
from minimalresponses.common import (
AEXAMPLE2_A,
AEXAMPLE4_A,
AROOTSERVER_NS,
EXAMPLE2_NS,
EXAMPLE4_NS,
INPUTPARAMS,
NSEXAMPLE2_A,
NSEXAMPLE4_A,
check,
reconfig,
)
INPUTS = [
# ns1 provides AUTHORITY and ADDITIONAL as it delegate those zone.
("ns1", "a.example2", "A", True, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", True, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
("ns1", "a.example2", "A", False, False, NOERROR, None, EXAMPLE2_NS, NSEXAMPLE2_A),
("ns1", "a.example4", "A", False, False, NOERROR, None, EXAMPLE4_NS, NSEXAMPLE4_A),
# ns2 behaves like `minimal-responses no` with RD=0 on `example2.`
# (which it makes authority on).
# Nothing for `example4.` as it's an authoritative server only,
# and doesn't own that zone.
# Otherwise, it behaves like `minimal-responses yes`
("ns2", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns2", "a.example4", "A", True, False, REFUSED, None, None, None),
(
"ns2",
"a.example2",
"A",
False,
False,
NOERROR,
AEXAMPLE2_A,
EXAMPLE2_NS,
NSEXAMPLE2_A,
),
("ns2", "a.example4", "A", False, False, REFUSED, None, None, None),
# ns3 behaviour (as resolver) is common between all variants with RD=1.
# With RD=0, it has the same behavior than `minimal-responses no;`
("ns3", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, False, NOERROR, AEXAMPLE4_A, None, None),
("ns3", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", True, True, NOERROR, AEXAMPLE4_A, None, None),
("ns3", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example4", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
("ns3", "a.example4", "A", False, True, NOERROR, AEXAMPLE4_A, None, None),
# ns4, with RD=1 has minimal responses (just the answer).
# But RD=0 behaves as `minimal-responses no`.
("ns4", "a.example2", "A", True, False, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example4", "A", True, False, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", True, True, NOERROR, AEXAMPLE2_A, None, None),
("ns4", "a.example4", "A", True, True, NOERROR, AEXAMPLE4_A, None, None),
("ns4", "a.example2", "A", False, False, NOERROR, None, AROOTSERVER_NS, None),
(
"ns4",
"a.example4",
"A",
False,
False,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
("ns4", "a.example2", "A", False, True, NOERROR, AEXAMPLE2_A, None, None),
(
"ns4",
"a.example4",
"A",
False,
True,
NOERROR,
AEXAMPLE4_A,
EXAMPLE4_NS,
NSEXAMPLE4_A,
),
# Resolver always provides glues with associated NS for qtype=NS
("ns3", "example2", "NS", True, False, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", False, False, NOERROR, None, AROOTSERVER_NS, None),
("ns3", "example2", "NS", False, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
("ns3", "example2", "NS", True, True, NOERROR, EXAMPLE2_NS, None, NSEXAMPLE2_A),
]
@pytest.fixture(scope="module", autouse=True)
def authsection_init(servers, templates):
reconfig(servers, templates, "no-auth-recursive")
@pytest.mark.parametrize(INPUTPARAMS, INPUTS)
def test_minimalresponses_noauthrec(
ns, qname, qtype, rd, cached, rcode, answer, authority, additional
):
check(ns, qname, qtype, rd, cached, rcode, answer, authority, additional)

View file

@ -0,0 +1,26 @@
# 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.
import pytest
from minimalresponses.common import INPUTPARAMS, INPUTS_YES_NOAUTH, check, reconfig
@pytest.fixture(scope="module", autouse=True)
def authsection_init(servers, templates):
reconfig(servers, templates, "yes")
@pytest.mark.parametrize(INPUTPARAMS, INPUTS_YES_NOAUTH)
def test_minimalresponses_yes(
ns, qname, qtype, rd, cached, rcode, answer, authority, additional
):
check(ns, qname, qtype, rd, cached, rcode, answer, authority, additional)

View file

@ -332,27 +332,6 @@ grep "foo.example.*IN.*A.*127.0.0.1" dig.out.ns3.test$n >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking that delegations from cache which improve mirror zone delegations are properly handled ($n)"
ret=0
# First, issue a recursive query in order to cache an RRset which is not within
# the mirror zone's bailiwick.
$DIG $DIGOPTS @10.53.0.3 sub.example. NS >dig.out.ns3.test$n.1 2>&1 || ret=1
# Ensure the child-side NS RRset is returned.
grep "NOERROR" dig.out.ns3.test$n.1 >/dev/null || ret=1
grep "ANSWER: 2" dig.out.ns3.test$n.1 >/dev/null || ret=1
grep "sub.example.*IN.*NS" dig.out.ns3.test$n.1 >/dev/null || ret=1
# Issue a non-recursive query for something below the cached zone cut.
$DIG $DIGOPTS @10.53.0.3 +norec foo.sub.example. A >dig.out.ns3.test$n.2 2>&1 || ret=1
# Ensure the cached NS RRset is returned in a delegation, along with the
# parent-side DS RRset.
grep "NOERROR" dig.out.ns3.test$n.2 >/dev/null || ret=1
grep "ANSWER: 0" dig.out.ns3.test$n.2 >/dev/null || ret=1
grep "sub.example.*IN.*NS" dig.out.ns3.test$n.2 >/dev/null || ret=1
grep "sub.example.*IN.*DS" dig.out.ns3.test$n.2 >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking flags set in a DNSKEY response sourced from a mirror zone ($n)"
ret=0

View file

@ -124,7 +124,6 @@ sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.good.
ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS bit.longer.ns.name.good.
@ -162,7 +161,6 @@ sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.good.
ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS bit.longer.ns.name.good.
@ -194,7 +192,6 @@ grep "status: NXDOMAIN" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns2.bad.
NS bad.
NS boing.bad.
__EOF
@ -216,7 +213,6 @@ cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.bad.
ADDR a.bit.longer.ns.name.bad.
ADDR icky.icky.icky.ptang.zoop.boing.bad.
ADDR ns2.bad.
ADDR ns3.bad.
ADDR ns3.bad.
NS boing.bad.
@ -242,7 +238,6 @@ grep "status: SERVFAIL" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns2.ugly.
NS boing.ugly.
NS ugly.
__EOF
@ -265,12 +260,10 @@ cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.ugly.
ADDR a.bit.longer.ns.name.ugly.
ADDR icky.icky.icky.ptang.zoop.boing.ugly.
ADDR ns2.ugly.
ADDR ns3.ugly.
ADDR ns3.ugly.
NS boing.ugly.
NS name.ugly.
NS name.ugly.
__EOF
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans3/query.log - >/dev/null || ret=1
echo "ADDR icky.icky.icky.ptang.zoop.boing.ugly." | diff ans4/query.log - >/dev/null || ret=1
@ -299,7 +292,6 @@ sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.slow.
ADDR a.bit.longer.ns.name.slow.
ADDR ns2.slow.
ADDR ns3.slow.
ADDR ns3.slow.
NS bit.longer.ns.name.slow.
@ -359,7 +351,6 @@ sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR a.bit.longer.ns.name.good.
ADDR a.bit.longer.ns.name.good.
ADDR ns2.good.
ADDR ns3.good.
ADDR ns3.good.
NS bit.longer.ns.name.good.
@ -449,15 +440,12 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
NS stale.
__EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@ -476,14 +464,11 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
__EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done
@ -519,15 +504,12 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
NS stale.
__EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
NS b.stale.
TXT a.b.stale.
__EOF
@ -546,14 +528,11 @@ grep "a\.b\.stale\..*1.*IN.*TXT.*hooray" dig.out.test$n >/dev/null || ret=1
sleep 1
sort ans2/query.log >ans2/query.log.sorted
cat <<__EOF | diff ans2/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
ADDR ns2.stale.
NS b.stale.
__EOF
test -f ans3/query.log && ret=1
sort ans4/query.log >ans4/query.log.sorted
cat <<__EOF | diff ans4/query.log.sorted - >/dev/null || ret=1
ADDR ns.b.stale.
TXT a.b.stale.
__EOF
for ans in ans2 ans3 ans4; do mv -f $ans/query.log query-$ans-$n.log 2>/dev/null || true; done

View file

@ -0,0 +1 @@
-m record -c named.conf -d 99 -D resolver-ns1 -g -T maxcachesize=3145728

View file

@ -76,13 +76,21 @@ n=$((n + 1))
echo_i "checking that the timeout didn't skew the resolver responses counters and did update the timeout counter ($n)"
ret=0
rndccmd 10.53.0.1 stats || ret=1
# the timeout query triggers two extra queries, so we expect a small increase
grep -F 'responses received' ns1/named.stats >ns1/named.stats.responses-after || true
read before _ <ns1/named.stats.responses-before || true
read after _ <ns1/named.stats.responses-after || true
[ $((after - before)) -lt 3 ] || ret=1
grep -F 'queries with RTT' ns1/named.stats >ns1/named.stats.rtt-after || true
read before _ <ns1/named.stats.rtt-before || true
read after _ <ns1/named.stats.rtt-after || true
[ $((after - before)) -lt 3 ] || ret=1
# ...but the timeout counter should increase by more
grep -F 'query timeouts' ns1/named.stats >ns1/named.stats.timeouts-after || true
mv ns1/named.stats ns1/named.stats-after
diff ns1/named.stats.responses-before ns1/named.stats.responses-after >/dev/null || ret=1
diff ns1/named.stats.rtt-before ns1/named.stats.rtt-after >/dev/null || ret=1
diff ns1/named.stats.timeouts-before ns1/named.stats.timeouts-after >/dev/null && ret=1
read before _ <ns1/named.stats.timeouts-before || true
read after _ <ns1/named.stats.timeouts-after || true
[ $((after - before)) -ge 3 ] || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
@ -431,24 +439,15 @@ grep "status: NOERROR" dig.ns5.prime.${n} >/dev/null || {
}
cp ns4/tld2.db ns4/tld.db
rndc_reload ns4 10.53.0.4 tld
old=
for i in 0 1 2 3 4 5 6 7 8 9; do
foo=0
dig_with_opts @10.53.0.5 ns$i.to-be-removed.tld A >/dev/null
dig_with_opts @10.53.0.5 www.to-be-removed.tld A >dig.ns5.out.${n}
grep "status: NXDOMAIN" dig.ns5.out.${n} >/dev/null || foo=1
[ $foo = 0 ] && break
$NSUPDATE <<EOF
server 10.53.0.6 ${PORT}
zone to-be-removed.tld
update add to-be-removed.tld 100 NS ns${i}.to-be-removed.tld
update delete to-be-removed.tld NS ns${old}.to-be-removed.tld
send
EOF
old=$i
sleep 1
# After 5 seconds, the delegation expires, so we expect NXDOMAIN
[ $i -gt 5 ] && [ $foo -eq 1 ] && ret=1
done
[ $ret = 0 ] && ret=$foo
if [ $ret != 0 ]; then
echo_i "failed"
status=1

View file

@ -0,0 +1,67 @@
"""
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.
"""
from collections.abc import AsyncGenerator
import dns.rcode
import dns.rdatatype
import dns.rrset
from isctest.asyncserver import (
AsyncDnsServer,
DnsResponseSend,
QueryContext,
ResponseHandler,
)
class ReplyA(ResponseHandler):
def match(self, qctx: QueryContext) -> bool:
return qctx.qtype == dns.rdatatype.A
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
a_rrset = dns.rrset.from_text(
qctx.qname, 300, qctx.qclass, dns.rdatatype.A, "10.53.0.6"
)
qctx.response.answer.append(a_rrset)
yield DnsResponseSend(qctx.response)
class DelayNs(ResponseHandler):
def match(self, qctx: QueryContext) -> bool:
return qctx.qtype == dns.rdatatype.NS
async def get_responses(
self, qctx: QueryContext
) -> AsyncGenerator[DnsResponseSend, None]:
ns_rrset = dns.rrset.from_text(
"www.bar.",
300,
qctx.qclass,
dns.rdatatype.NS,
"ns6.foo.",
)
qctx.response.answer.append(ns_rrset)
yield DnsResponseSend(qctx.response, delay=1)
def main() -> None:
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
server.install_response_handlers(ReplyA(), DelayNs())
server.run()
if __name__ == "__main__":
main()

View file

@ -13,3 +13,6 @@ $TTL 0
@ SOA . . 0 0 0 0 0
@ NS .
32.100.0.53.10.rpz-nsip CNAME .
; Return NXDOMAIN is the NS name of the zone is `ns6.foo.`
ns6.foo.rpz-nsdname CNAME .

View file

@ -15,3 +15,5 @@ $TTL 0
ns A 10.53.0.3
foo NS ns5.foo
ns5.foo A 10.53.0.5
bar NS ns6.foo
ns6.foo A 10.53.0.6

View file

@ -64,7 +64,7 @@ run_query() {
LINE=$2
NAME=$(sed -n -e "$LINE,"'$p' ns2/$TESTNAME.queries | head -n 1)
$DIG $DIGOPTS $NAME a @10.53.0.2 -p ${PORT} -b 127.0.0.1 >dig.out.${t}
$DIG $DIGOPTS $NAME a @10.53.0.2 -p ${PORT} -b 127.0.0.1 >dig.out.${t} || return 1
grep "status: SERVFAIL" dig.out.${t} >/dev/null 2>&1 && return 1
return 0
}
@ -111,7 +111,7 @@ add_test_marker() {
t=$((t + 1))
echo_i "testing that l1.l0 exists without RPZ (${t})"
add_test_marker 10.53.0.2
$DIG $DIGOPTS l1.l0 ns @10.53.0.2 -p ${PORT} >dig.out.${t}
$DIG $DIGOPTS l1.l0 ns @10.53.0.2 -p ${PORT} >dig.out.${t} || status=1
grep "status: NOERROR" dig.out.${t} >/dev/null 2>&1 || {
echo_i "test ${t} failed"
status=1
@ -120,7 +120,7 @@ grep "status: NOERROR" dig.out.${t} >/dev/null 2>&1 || {
t=$((t + 1))
echo_i "testing that l2.l1.l0 returns SERVFAIL without RPZ (${t})"
add_test_marker 10.53.0.2
$DIG $DIGOPTS l2.l1.l0 ns @10.53.0.2 -p ${PORT} >dig.out.${t}
$DIG $DIGOPTS l2.l1.l0 ns @10.53.0.2 -p ${PORT} >dig.out.${t} || status=1
grep "status: SERVFAIL" dig.out.${t} >/dev/null 2>&1 || {
echo_i "test ${t} failed"
status=1
@ -206,7 +206,7 @@ sleep 1
t=$((t + 1))
echo_i "running dig to cache CNAME record (${t})"
add_test_marker 10.53.0.1 10.53.0.2
$DIG $DIGOPTS @10.53.0.2 -p ${PORT} www.test.example.org CNAME >dig.out.${t}
$DIG $DIGOPTS @10.53.0.2 -p ${PORT} www.test.example.org CNAME >dig.out.${t} || status=1
sleep 1
echo_i "suspending authority server"
PID=$(cat ns1/named.pid)
@ -248,7 +248,7 @@ sleep 1
t=$((t + 1))
echo_i "running dig to cache CNAME record (${t})"
add_test_marker 10.53.0.1 10.53.0.2
$DIG $DIGOPTS @10.53.0.2 -p ${PORT} www.test.example.org CNAME >dig.out.${t}
$DIG $DIGOPTS @10.53.0.2 -p ${PORT} www.test.example.org CNAME >dig.out.${t} || status=1
sleep 1
echo_i "suspending authority server"
PID=$(cat ns1/named.pid)
@ -289,7 +289,7 @@ add_test_marker 10.53.0.2
run_server max
i=1
while test $i -le 64; do
$DIG $DIGOPTS name$i a @10.53.0.2 -p ${PORT} -b 10.53.0.1 >dig.out.${t}.${i}
$DIG $DIGOPTS name$i a @10.53.0.2 -p ${PORT} -b 10.53.0.1 >dig.out.${t}.${i} || status=1
grep "^name$i.[ ]*[0-9]*[ ]*IN[ ]*A[ ]*10.53.0.$i" dig.out.${t}.${i} >/dev/null 2>&1 || {
echo_i "test $t failed: didn't get expected answer from policy zone $i"
status=1
@ -302,7 +302,7 @@ t=$((t + 1))
echo_i "testing CLIENT-IP behavior (${t})"
add_test_marker 10.53.0.2
run_server clientip
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t} || status=1
grep "status: NOERROR" dig.out.${t} >/dev/null 2>&1 || {
echo_i "test $t failed: query failed"
status=1
@ -317,17 +317,17 @@ t=$((t + 1))
echo_i "testing CLIENT-IP behavior #2 (${t})"
add_test_marker 10.53.0.2
run_server clientip2
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.1 >dig.out.${t}.1
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.1 >dig.out.${t}.1 || status=1
grep "status: SERVFAIL" dig.out.${t}.1 >/dev/null 2>&1 || {
echo_i "test $t failed: query failed"
status=1
}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.2 >dig.out.${t}.2
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.2 >dig.out.${t}.2 || status=1
grep "status: NXDOMAIN" dig.out.${t}.2 >/dev/null 2>&1 || {
echo_i "test $t failed: query failed"
status=1
}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.3 >dig.out.${t}.3
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.3 >dig.out.${t}.3 || status=1
grep "status: NOERROR" dig.out.${t}.3 >/dev/null 2>&1 || {
echo_i "test $t failed: query failed"
status=1
@ -336,7 +336,7 @@ grep "^l2.l1.l0.[ ]*[0-9]*[ ]*IN[ ]*A[ ]*10.53.0.1" dig.out.${t}.3 >/dev/nul
echo_i "test $t failed: didn't get expected answer"
status=1
}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t}.4
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t}.4 || status=1
grep "status: SERVFAIL" dig.out.${t}.4 >/dev/null 2>&1 || {
echo_i "test $t failed: query failed"
status=1
@ -348,7 +348,7 @@ echo_i "testing RPZ log clause (${t})"
add_test_marker 10.53.0.2
run_server log
cur=$(awk 'BEGIN {l=0} /^/ {l++} END { print l }' ns2/named.run)
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.4 >dig.out.${t} || status=1
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.3 >>dig.out.${t}
$DIG $DIGOPTS l2.l1.l0 a @10.53.0.2 -p ${PORT} -b 10.53.0.2 >>dig.out.${t}
sed -n "$cur,"'$p' <ns2/named.run | grep "view recursive: rpz CLIENT-IP Local-Data rewrite l2.l1.l0/A/IN via 32.4.0.53.10.rpz-client-ip.log1" >/dev/null && {
@ -370,7 +370,7 @@ t=$((t + 1))
echo_i "testing wildcard behavior with 1 RPZ zone (${t})"
add_test_marker 10.53.0.2
run_server wildcard1
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1 || status=1
grep "status: NXDOMAIN" dig.out.${t}.1 >/dev/null || {
echo_i "test ${t} failed"
status=1
@ -385,7 +385,7 @@ t=$((t + 1))
echo_i "testing wildcard behavior with 2 RPZ zones (${t})"
add_test_marker 10.53.0.2
run_server wildcard2
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1 || status=1
grep "status: NXDOMAIN" dig.out.${t}.1 >/dev/null || {
echo_i "test ${t} failed"
status=1
@ -400,7 +400,7 @@ t=$((t + 1))
echo_i "testing wildcard behavior with 1 RPZ zone and no non-wildcard triggers (${t})"
add_test_marker 10.53.0.2
run_server wildcard3
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1
$DIG $DIGOPTS www.test1.example.net a @10.53.0.2 -p ${PORT} >dig.out.${t}.1 || status=1
grep "status: NXDOMAIN" dig.out.${t}.1 >/dev/null || {
echo_i "test ${t} failed"
status=1
@ -415,7 +415,7 @@ t=$((t + 1))
echo_i "testing wildcard passthru before explicit drop (${t})"
add_test_marker 10.53.0.2
run_server wildcard4
$DIG $DIGOPTS example.com a @10.53.0.2 -p ${PORT} >dig.out.${t}.1
$DIG $DIGOPTS example.com a @10.53.0.2 -p ${PORT} >dig.out.${t}.1 || status=1
grep "status: NOERROR" dig.out.${t}.1 >/dev/null || {
echo_i "test ${t} failed"
status=1
@ -436,6 +436,14 @@ grep "invalid rpz IP address \"1000.4.0.53.10.rpz-client-ip.invalidprefixlength\
status=1
}
t=$((t + 1))
echo_i "checking NSDNAME policy being blocked' ($t)"
$DIG $DIGOPTS www.bar. @10.53.0.3 -p ${PORT} >dig.out.${t}
grep "status: NXDOMAIN" dig.out.${t} >/dev/null || {
echo_i "test ${t} failed"
status=1
}
t=$((t + 1))
echo_i "checking 'nsip-wait-recurse no' is faster than 'nsip-wait-recurse yes' ($t)"
add_test_marker 10.53.0.2 10.53.0.3

View file

@ -208,25 +208,5 @@ grep "status: NOERROR" dig.out.ns2.soa.test$n >/dev/null || ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
n=$((n + 1))
echo_i "checking static-stub synthesised NS is not returned ($n)"
ret=0
$DIG $DIGOPTS unsigned. @10.53.0.2 ns >dig.out.ns2.ns.test$n || ret=1
sleep 2
$DIG $DIGOPTS data.unsigned @10.53.0.2 txt >dig.out.ns2.txt1.test$n || ret=1
sleep 4
$DIG $DIGOPTS data.unsigned @10.53.0.2 txt >dig.out.ns2.txt2.test$n || ret=1
grep "status: NOERROR" dig.out.ns2.ns.test$n >/dev/null || ret=1
grep "status: NOERROR" dig.out.ns2.txt1.test$n >/dev/null || ret=1
# NS RRset from zone is returned
grep '^unsigned\..*NS.ns\.unsigned\.$' dig.out.ns2.txt1.test$n >/dev/null || ret=1
grep '^unsigned\..*NS.unsigned\.$' dig.out.ns2.txt1.test$n >/dev/null && ret=1
# NS expired and synthesised response is not returned
grep "status: NOERROR" dig.out.ns2.txt2.test$n >/dev/null || ret=1
grep '^unsigned\..*NS.ns\.unsigned\.$' dig.out.ns2.txt2.test$n >/dev/null && ret=1
grep '^unsigned\..*NS.unsigned\.$' dig.out.ns2.txt2.test$n >/dev/null && ret=1
if [ $ret != 0 ]; then echo_i "failed"; fi
status=$((status + ret))
echo_i "exit status: $status"
[ $status -eq 0 ] || exit 1

View file

@ -2054,21 +2054,19 @@ Boolean Options
:any:`minimal-responses` takes one of four values:
- ``no``: the server is as complete as possible when generating
responses.
responses from authoritative zones. (Authority records are not
added when answering from the cache.)
- ``yes``: the server only adds records to the authority and additional
sections when such records are required by the DNS protocol (for
example, when returning delegations or negative responses). This
provides the best server performance but may result in more client
queries.
- ``no-auth``: the server omits records from the authority section except
when they are required, but it may still add records to the
additional section.
- ``no-auth-recursive``: the same as ``no-auth`` when recursion is requested
- ``no-auth``: is a deprecated alias of ``yes``.
- ``no-auth-recursive``: the same as ``yes`` when recursion is requested
in the query (RD=1), or the same as ``no`` if recursion is not requested.
``no-auth`` and ``no-auth-recursive`` are useful when answering stub
clients, which usually ignore the authority section.
``no-auth-recursive`` is meant for use in mixed-mode servers that
``no-auth-recursive`` is useful when answering stub clients, which usually
ignore the authority section. It is meant for use in mixed-mode servers that
handle both authoritative and recursive queries.
The default is ``no-auth-recursive``.

View file

@ -1714,6 +1714,96 @@ dns_adb_shutdown(dns_adb_t *adb) {
isc_async_run(isc_loop_main(), dns_adb_shutdown_async, adb);
}
static void
findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *addr,
dns_adbaddrinfo_t **adbaddrp, isc_stdtime_t now,
unsigned int options) {
dns_adbentry_t *adbentry = get_attached_and_locked_entry(adb, now,
addr);
if ((options & DNS_ADBFIND_QUOTAEXEMPT) == 0 &&
adbentry_overquota(adbentry))
{
goto out;
}
in_port_t port = isc_sockaddr_getport(addr);
*adbaddrp = new_adbaddrinfo(adb, adbentry, port);
out:
UNLOCK(&adbentry->lock);
dns_adbentry_detach(&adbentry);
}
void
dns_adb_createaddrinfosfind(dns_adb_t *adb, isc_netaddrlist_t *addrs,
in_port_t port, unsigned int options,
isc_stdtime_t now, size_t maxaddrs,
dns_adbfind_t **findp, size_t *findlen) {
dns_adbfind_t *find = NULL;
isc_sockaddr_t sockaddr = {};
REQUIRE(DNS_ADB_VALID(adb));
REQUIRE(addrs != NULL);
REQUIRE(findp != NULL && *findp == NULL);
rcu_read_lock();
if (atomic_load(&adb->shuttingdown)) {
rcu_read_unlock();
return;
}
if (now == 0) {
now = isc_stdtime_now();
}
find = new_adbfind(adb, port);
ISC_LIST_FOREACH(*addrs, addrlink, link) {
dns_adbaddrinfo_t *addrinfo = NULL;
sockaddr.type.sa.sa_family = addrlink->addr.family;
switch (addrlink->addr.family) {
case AF_INET:
if ((options & DNS_ADBFIND_INET) == 0) {
continue;
}
sockaddr.type.sin.sin_addr = addrlink->addr.type.in;
sockaddr.type.sin.sin_port = htons(port);
break;
case AF_INET6:
if ((options & DNS_ADBFIND_INET6) == 0) {
continue;
}
/*
* TODO: findaddrinfo() compares the scope, this might
* be a problem...
*/
sockaddr.type.sin6.sin6_addr = addrlink->addr.type.in6;
sockaddr.type.sin6.sin6_port = htons(port);
break;
default:
UNREACHABLE();
}
findaddrinfo(adb, &sockaddr, &addrinfo, now, options);
if (addrinfo == NULL) {
find->options |= DNS_ADBFIND_OVERQUOTA;
continue;
}
ISC_LIST_APPEND(find->list, addrinfo, publink);
(*findlen)++;
if (maxaddrs - *findlen == 0) {
break;
}
}
*findp = find;
rcu_read_unlock();
}
/*
* Look up the name in our internal database.
*
@ -2741,8 +2831,7 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
dns_adb_t *adb = NULL;
dns_fixedname_t fixed;
dns_name_t *name = NULL;
dns_rdataset_t rdataset;
dns_rdataset_t *nameservers = NULL;
dns_delegset_t *delegset = NULL;
unsigned int options = no_validation ? DNS_FETCHOPT_NOVALIDATE : 0;
REQUIRE(DNS_ADBNAME_VALID(adbname));
@ -2756,15 +2845,12 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
adbname->fetch_err = FIND_ERR_NOTFOUND;
dns_rdataset_init(&rdataset);
if (start_at_zone) {
DP(ENTER_LEVEL, "fetch_name: starting at zone for name %p",
adbname);
name = dns_fixedname_initname(&fixed);
CHECK(dns_view_bestzonecut(adb->view, adbname->name, name, NULL,
0, 0, true, false, &rdataset));
nameservers = &rdataset;
0, 0, true, false, &delegset));
options |= DNS_FETCHOPT_UNSHARED;
} else if (adb->view->qminimization) {
options |= DNS_FETCHOPT_QMINIMIZE | DNS_FETCHOPT_QMIN_SKIP_IP6A;
@ -2785,7 +2871,7 @@ fetch_name(dns_adbname_t *adbname, bool start_at_zone, bool no_validation,
*/
dns_adbname_ref(adbname);
result = dns_resolver_createfetch(
adb->res, adbname->name, type, name, nameservers, NULL, NULL, 0,
adb->res, adbname->name, type, name, delegset, NULL, NULL, 0,
options, depth, qc, gqc, parent, isc_loop(), fetch_callback,
adbname, NULL, &fetch->rdataset, NULL, &fetch->fetch);
if (result != ISC_R_SUCCESS) {
@ -2808,7 +2894,10 @@ cleanup:
if (fetch != NULL) {
free_adbfetch(adb, &fetch);
}
dns_rdataset_cleanup(&rdataset);
if (delegset != NULL) {
dns_delegset_detach(&delegset);
}
return result;
}
@ -3138,14 +3227,7 @@ dns_adb_findaddrinfo(dns_adb_t *adb, const isc_sockaddr_t *addr,
return ISC_R_SHUTTINGDOWN;
}
dns_adbentry_t *adbentry = get_attached_and_locked_entry(adb, now,
addr);
in_port_t port = isc_sockaddr_getport(addr);
*adbaddrp = new_adbaddrinfo(adb, adbentry, port);
UNLOCK(&adbentry->lock);
dns_adbentry_detach(&adbentry);
findaddrinfo(adb, addr, adbaddrp, now, DNS_ADBFIND_QUOTAEXEMPT);
rcu_read_unlock();

View file

@ -526,34 +526,6 @@ dns__db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
return ISC_R_NOTIMPLEMENTED;
}
isc_result_t
dns__db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
isc_stdtime_t now, dns_dbnode_t **nodep,
dns_name_t *foundname, dns_name_t *dcname,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
/*
* Find the deepest known zonecut which encloses 'name' in 'db'.
* foundname is the zonecut, dcname is the deepest name we have
* in database that is part of queried name.
*/
REQUIRE(DNS_DB_VALID(db));
REQUIRE((db->attributes & DNS_DBATTR_CACHE) != 0);
REQUIRE(nodep == NULL || *nodep == NULL);
REQUIRE(dns_name_hasbuffer(foundname));
REQUIRE(sigrdataset == NULL ||
(DNS_RDATASET_VALID(sigrdataset) &&
!dns_rdataset_isassociated(sigrdataset)));
if (db->methods->findzonecut != NULL) {
return (db->methods->findzonecut)(
db, name, options, now, nodep, foundname, dcname,
rdataset, sigrdataset DNS__DB_FLARG_PASS);
}
return ISC_R_NOTIMPLEMENTED;
}
void
dns__db_attachnode(dns_dbnode_t *source, dns_dbnode_t **targetp DNS__DB_FLARG) {
/*

988
lib/dns/deleg.c Normal file
View file

@ -0,0 +1,988 @@
/*
* 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.
*/
#include <isc/async.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/netaddr.h>
#include <isc/sieve.h>
#include <isc/stdtime.h>
#include <isc/urcu.h>
#include <isc/uv.h>
#include <dns/deleg.h>
#include <dns/name.h>
#include <dns/qp.h>
#include <dns/view.h>
#define DELEGDB_NODE_MAGIC ISC_MAGIC('D', 'e', 'G', 'N')
#define VALID_DELEGDB_NODE(node) ISC_MAGIC_VALID(node, DELEGDB_NODE_MAGIC)
#define DELEGDB_MAGIC ISC_MAGIC('D', 'e', 'G', 'D')
#define VALID_DELEGDB(db) ISC_MAGIC_VALID(db, DELEGDB_MAGIC)
#define DELEGDB_MINSIZE (1024 * 1024) /* 1MiB */
typedef struct delegdb_node delegdb_node_t;
struct dns_delegdb {
unsigned int magic;
/*
* The DB uses its own memory context in order to easily enforce
* overmem policies based on allocations made from this memory context.
*/
isc_mem_t *mctx;
isc_refcount_t references;
size_t nloops;
ISC_SIEVE(delegdb_node_t) * lru;
dns_qpmulti_t *nodes;
/*
* Keep track of now many owners are actually using the delegdb. For
* instance:
*
* - During a server reload, the new view will (by default)
* start owning the existing delegdb from the previous instance of the
* same view using `dns_delegdb_reuse()`. This will increase `owners`
* by one.
*
* - Later on, either the old instance of the view (or the new one,
* in case of reload failure) will call `dns_delegdb_shutdown()` on
* the delegdb. This will decrement `owners` by one.
*
* If `owners` is bigger than 1 when `dns_delegdb_shutdown()` is called,
* it means the delegdb must not be shutdown because there are other
* owners using it, so `dns_delegdb_shutdown()` bails off in this case.
* (After decrementing `owners`.)
*/
isc_refcount_t owners;
size_t lowater;
size_t hiwater;
};
static void
delegdb_destroy(dns_delegdb_t *delegdb) {
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(delegdb->nodes == NULL);
delegdb->magic = 0;
isc_mem_cput(delegdb->mctx, delegdb->lru, delegdb->nloops,
sizeof(delegdb->lru[0]));
isc_mem_putanddetach(&delegdb->mctx, delegdb, sizeof(*delegdb));
}
ISC_REFCOUNT_IMPL(dns_delegdb, delegdb_destroy);
struct delegdb_node {
unsigned int magic;
dns_delegdb_t *delegdb;
isc_refcount_t references;
/* LRU */
isc_loop_t *loop;
ISC_LINK(delegdb_node_t) link;
bool visited;
/*
* Used to build a list of nodes to be deleted (when running the
* delete tree flow).
*/
ISC_LINK(delegdb_node_t) deadlink;
/*
* Immutable node data
*/
size_t size;
dns_name_t zonecut;
dns_delegset_t *delegset;
};
/*
* All node cleanup is done on the node's owning loop so that the node
* remains fully valid (name, delegset, SIEVE link) until it is actually
* destroyed. This is important because after a node is removed from the
* QP trie, it may still be linked in the owning loop's SIEVE list; if
* another thread's eviction could encounter a half-destroyed node, we
* would get a use-after-free. By deferring everything to the owning
* loop, the node is intact until the SIEVE unlink happens.
*/
static void
delegdb_node_destroy_async(void *arg) {
delegdb_node_t *node = arg;
isc_mem_t *mctx = NULL;
REQUIRE(VALID_DELEGDB_NODE(node));
REQUIRE(DNS_DELEGSET_VALID(node->delegset));
node->magic = 0;
isc_mem_attach(node->delegdb->mctx, &mctx);
if (ISC_SIEVE_LINKED(node, link)) {
ISC_SIEVE_UNLINK(node->delegdb->lru[isc_tid()], node, link);
}
dns_name_free(&node->zonecut, mctx);
dns_delegset_detach(&node->delegset);
dns_delegdb_detach(&node->delegdb);
isc_loop_unref(node->loop);
isc_mem_putanddetach(&mctx, node, sizeof(*node));
}
static void
delegdb_node_destroy(delegdb_node_t *node) {
REQUIRE(VALID_DELEGDB_NODE(node));
if (node->loop == isc_loop()) {
delegdb_node_destroy_async(node);
} else {
isc_async_run(node->loop, delegdb_node_destroy_async, node);
}
}
#ifdef DNS_DELEGDB_NODETRACE
#define delegdb_node_ref(ptr) \
delegdb_node__ref(ptr, __func__, __FILE__, __LINE__)
#define delegdb_node_unref(ptr) \
delegdb_node__unref(ptr, __func__, __FILE__, __LINE__)
ISC_REFCOUNT_STATIC_TRACE_DECL(delegdb_node);
ISC_REFCOUNT_STATIC_TRACE_IMPL(delegdb_node, delegdb_node_destroy);
#else
ISC_REFCOUNT_STATIC_DECL(delegdb_node);
ISC_REFCOUNT_STATIC_IMPL(delegdb_node, delegdb_node_destroy);
#endif
static void
dbnode_attach(ISC_ATTR_UNUSED void *uctx, void *pval,
ISC_ATTR_UNUSED uint32_t ival) {
delegdb_node_t *node = pval;
REQUIRE(VALID_DELEGDB_NODE(node));
delegdb_node_ref(node);
}
static void
dbnode_detach(ISC_ATTR_UNUSED void *uctx, void *pval,
ISC_ATTR_UNUSED uint32_t ival) {
delegdb_node_t *node = pval;
REQUIRE(VALID_DELEGDB_NODE(node));
delegdb_node_unref(node);
}
static size_t
makekey(dns_qpkey_t key, void *uctx ISC_ATTR_UNUSED, void *pval,
uint32_t ival ISC_ATTR_UNUSED) {
delegdb_node_t *data = pval;
return dns_qpkey_fromname(key, &data->zonecut, DNS_DBNAMESPACE_NORMAL);
}
static void
triename(ISC_ATTR_UNUSED void *uctx, char *buf, size_t size) {
(void)strncpy(buf, "delegdb", size);
}
static dns_qpmethods_t qpmethods = { .attach = dbnode_attach,
.detach = dbnode_detach,
.makekey = makekey,
.triename = triename };
void
dns_delegdb_create(dns_delegdb_t **delegdbp) {
isc_mem_t *mctx = NULL;
dns_delegdb_t *delegdb = NULL;
REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
REQUIRE(delegdbp != NULL && *delegdbp == NULL);
isc_mem_create("dns_delegdb", &mctx);
isc_mem_setdestroycheck(mctx, true);
delegdb = isc_mem_get(mctx, sizeof(*delegdb));
*delegdb = (dns_delegdb_t){ .magic = DELEGDB_MAGIC,
.mctx = mctx,
.references = ISC_REFCOUNT_INITIALIZER(1),
.nloops = isc_loopmgr_nloops(),
.owners = ISC_REFCOUNT_INITIALIZER(1) };
dns_qpmulti_create(mctx, &qpmethods, &delegdb->nodes, &delegdb->nodes);
delegdb->lru = isc_mem_cget(mctx, delegdb->nloops,
sizeof(delegdb->lru[0]));
for (size_t i = 0; i < delegdb->nloops; i++) {
ISC_SIEVE_INIT(delegdb->lru[i]);
}
*delegdbp = delegdb;
}
void
dns_delegdb_reuse(dns_view_t *oldview, dns_view_t *newview) {
REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
REQUIRE(DNS_VIEW_VALID(oldview));
REQUIRE(DNS_VIEW_VALID(newview));
dns_delegdb_attach(oldview->deleg, &newview->deleg);
isc_refcount_increment(&oldview->deleg->owners);
}
typedef struct nodes_rcu_head {
isc_mem_t *mctx;
dns_qpmulti_t *nodes;
struct rcu_head rcu_head;
} nodes_rcu_head_t;
static void
deleg_destroy_qpmulti(struct rcu_head *rcu_head) {
nodes_rcu_head_t *nrh = caa_container_of(rcu_head, nodes_rcu_head_t,
rcu_head);
dns_qpmulti_destroy(&nrh->nodes);
isc_mem_putanddetach(&nrh->mctx, nrh, sizeof(*nrh));
}
inline static bool
isactive(delegdb_node_t *node, dns_ttl_t now) {
return node->delegset->expires > now;
}
static void
getparentnode(dns_qpchain_t *chain, delegdb_node_t **node, dns_ttl_t now) {
size_t len = dns_qpchain_length(chain);
while (len >= 2) {
*node = NULL;
dns_qpchain_node(chain, len - 2, (void **)node, NULL);
if (isactive(*node, now)) {
break;
}
len--;
}
}
/*
* NOTE: Caller needs to hold a RCU read critical section.
*/
static isc_result_t
dns__deleg_lookup(dns_delegdb_t *delegdb, dns_qpread_t *qpr,
const dns_name_t *name, isc_stdtime_t optnow,
unsigned int options, dns_name_t *zonecut,
dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
isc_result_t result = ISC_R_SUCCESS;
delegdb_node_t *node = NULL;
isc_stdtime_t now = optnow > 0 ? optnow : isc_stdtime_now();
dns_qpchain_t chain = {};
bool noexact = (options & DNS_DBFIND_NOEXACT) != 0;
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(DNS_NAME_VALID(name));
REQUIRE(dns_name_hasbuffer(zonecut));
REQUIRE(deepestzonecut == NULL || dns_name_hasbuffer(deepestzonecut));
result = dns_qp_lookup(qpr, name, DNS_DBNAMESPACE_NORMAL, NULL, &chain,
(void **)&node, NULL);
if (result != ISC_R_SUCCESS && result != DNS_R_PARTIALMATCH) {
return ISC_R_NOTFOUND;
}
INSIST(VALID_DELEGDB_NODE(node));
if (deepestzonecut != NULL) {
dns_name_copy(&node->zonecut, deepestzonecut);
}
if (result == ISC_R_SUCCESS && (noexact || !isactive(node, now))) {
getparentnode(&chain, &node, now);
} else if (result == DNS_R_PARTIALMATCH && !isactive(node, now)) {
getparentnode(&chain, &node, now);
}
result = isactive(node, now) ? ISC_R_SUCCESS : ISC_R_NOTFOUND;
if (result == ISC_R_SUCCESS) {
dns_name_copy(&node->zonecut, zonecut);
INSIST(node->delegset);
dns_delegset_attach(node->delegset, delegsetp);
ISC_SIEVE_MARK(node, visited);
} else {
/*
* FIXME: if we lookup something that has expired, we need
* either the "deadnodes" (see qpcache) mechanism here - or call
* something like isc_async_run(delete_me, node).
*/
}
return result;
}
isc_result_t
dns_delegdb_lookup(dns_delegdb_t *delegdb, const dns_name_t *name,
isc_stdtime_t now, unsigned int options, dns_name_t *zonecut,
dns_name_t *deepestzonecut, dns_delegset_t **delegsetp) {
isc_result_t result = ISC_R_SHUTTINGDOWN;
dns_qpmulti_t *nodes = NULL;
dns_qpread_t qpr = {};
rcu_read_lock();
nodes = rcu_dereference(delegdb->nodes);
if (nodes != NULL) {
dns_qpmulti_query(nodes, &qpr);
result = dns__deleg_lookup(delegdb, &qpr, name, now, options,
zonecut, deepestzonecut, delegsetp);
dns_qpread_destroy(nodes, &qpr);
}
rcu_read_unlock();
return result;
}
void
dns_delegset_allocset(dns_delegdb_t *delegdb, dns_delegset_t **delegsetp) {
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(delegsetp != NULL && *delegsetp == NULL);
dns_delegset_t *delegset = isc_mem_get(delegdb->mctx,
sizeof(*delegset));
*delegset = (dns_delegset_t){
.magic = DNS_DELEGSET_MAGIC,
.references = ISC_REFCOUNT_INITIALIZER(1),
.delegs = ISC_LIST_INITIALIZER,
};
isc_mem_attach(delegdb->mctx, &delegset->mctx);
*delegsetp = delegset;
}
void
dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type,
dns_deleg_t **delegp) {
dns_deleg_t *deleg = NULL;
REQUIRE(DNS_DELEGSET_VALID(delegset));
REQUIRE(delegp != NULL && *delegp == NULL);
REQUIRE(type != DNS_DELEGTYPE_UNDEFINED);
deleg = isc_mem_get(delegset->mctx, sizeof(*deleg));
*deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
.names = ISC_LIST_INITIALIZER,
.type = type,
.link = ISC_LINK_INITIALIZER };
ISC_LIST_APPEND(delegset->delegs, deleg, link);
*delegp = deleg;
}
void
dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg,
const isc_netaddr_t *addr) {
isc_netaddrlink_t *addrlink = NULL;
REQUIRE(DNS_DELEGSET_VALID(delegset));
REQUIRE(deleg != NULL);
REQUIRE(addr != NULL);
REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
deleg->type == DNS_DELEGTYPE_NS_GLUES);
addrlink = isc_mem_get(delegset->mctx, sizeof(*addrlink));
*addrlink = (isc_netaddrlink_t){ .addr = *addr,
.link = ISC_LINK_INITIALIZER };
ISC_LIST_APPEND(deleg->addresses, addrlink, link);
}
static void
addname(dns_delegset_t *delegset, dns_namelist_t *list,
const dns_name_t *name) {
dns_name_t *clone = NULL;
REQUIRE(DNS_DELEGSET_VALID(delegset));
REQUIRE(DNS_NAME_VALID(name));
clone = isc_mem_get(delegset->mctx, sizeof(*clone));
dns_name_init(clone);
dns_name_dup(name, delegset->mctx, clone);
ISC_LIST_APPEND(*list, clone, link);
}
void
dns_delegset_adddelegparam(dns_delegset_t *delegset, dns_deleg_t *deleg,
const dns_name_t *name) {
REQUIRE(deleg != NULL);
REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_PARAMS);
addname(delegset, &deleg->names, name);
}
void
dns_delegset_addns(dns_delegset_t *delegset, dns_deleg_t *deleg,
const dns_name_t *name) {
REQUIRE(deleg != NULL);
REQUIRE(deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
deleg->type == DNS_DELEGTYPE_NS_NAMES);
addname(delegset, &deleg->names, name);
}
static void
delegdb_cleanup(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes) {
dns_qp_t *qp = NULL;
delegdb_node_t *node = NULL;
size_t reclaimed = 0;
size_t requested = 0;
if (!isc_mem_isovermem(delegdb->mctx)) {
return;
}
requested = delegdb->hiwater - delegdb->lowater;
dns_qpmulti_write(nodes, &qp);
while (reclaimed < requested) {
node = ISC_SIEVE_NEXT(delegdb->lru[isc_tid()], visited, link);
if (node == NULL) {
break;
}
reclaimed += node->size;
ISC_SIEVE_UNLINK(delegdb->lru[isc_tid()], node, link);
(void)dns_qp_deletename(qp, &node->zonecut,
DNS_DBNAMESPACE_NORMAL, NULL, NULL);
}
dns_qp_compact(qp, DNS_QPGC_ALL);
dns_qpmulti_commit(nodes, &qp);
}
static size_t
delegset_size(dns_delegset_t *delegset) {
size_t sz = 0;
sz += sizeof(*delegset);
ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
sz += sizeof(*deleg);
ISC_LIST_FOREACH(deleg->addresses, address, link) {
sz += sizeof(*address);
}
ISC_LIST_FOREACH(deleg->names, name, link) {
sz += sizeof(*name) + dns_name_size(name);
}
}
return sz;
}
static size_t
delegdb_node_size(const dns_name_t *zonecut, dns_delegset_t *delegset) {
size_t sz = 0;
sz += sizeof(delegdb_node_t);
sz += dns_name_size(zonecut);
sz += delegset_size(delegset);
return sz;
}
static void
delegdb_node_prepare(dns_delegdb_t *delegdb, dns_qpmulti_t *nodes,
isc_stdtime_t now, dns_ttl_t ttl,
const dns_name_t *zonecut, dns_delegset_t *delegset,
delegdb_node_t **nodep) {
delegdb_cleanup(delegdb, nodes);
if (ttl == 0) {
ttl = 1;
}
delegset->expires = ttl + now;
*nodep = isc_mem_get(delegdb->mctx, sizeof(**nodep));
**nodep =
(delegdb_node_t){ .magic = DELEGDB_NODE_MAGIC,
.references = ISC_REFCOUNT_INITIALIZER(1),
.zonecut = DNS_NAME_INITEMPTY,
.link = ISC_LINK_INITIALIZER,
.deadlink = ISC_LINK_INITIALIZER,
.size = delegdb_node_size(zonecut, delegset),
.loop = isc_loop_ref(isc_loop()) };
dns_delegdb_attach(delegdb, &(*nodep)->delegdb);
dns_delegset_attach(delegset, &(*nodep)->delegset);
dns_name_dup(zonecut, delegdb->mctx, &(*nodep)->zonecut);
}
isc_result_t
dns_delegset_insert(dns_delegdb_t *delegdb, const dns_name_t *zonecut,
dns_ttl_t ttl, dns_delegset_t *delegset) {
isc_result_t result;
delegdb_node_t *node = NULL;
dns_qp_t *qp = NULL;
dns_qpread_t qpr = {};
isc_stdtime_t now = isc_stdtime_now();
dns_qpmulti_t *nodes = NULL;
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(DNS_NAME_VALID(zonecut));
REQUIRE(DNS_DELEGSET_VALID(delegset));
/*
* Only delegset allocated by the delegdb memory context can be added in
* the delegdb. This exclude transient delegset built from rdataset (see
* dns_delegset_fromrdataset()).
*/
REQUIRE(delegset->mctx == delegdb->mctx);
rcu_read_lock();
nodes = rcu_dereference(delegdb->nodes);
if (nodes == NULL) {
rcu_read_unlock();
return ISC_R_SHUTTINGDOWN;
}
/*
* First, check (without write txn) if the node already exists and is
* still valid.
*/
dns_qpmulti_query(nodes, &qpr);
result = dns_qp_lookup(&qpr, zonecut, DNS_DBNAMESPACE_NORMAL, NULL,
NULL, (void **)&node, NULL);
if (result == ISC_R_SUCCESS) {
INSIST(VALID_DELEGDB_NODE(node));
if (node->delegset->expires > now) {
dns_qpread_destroy(nodes, &qpr);
CLEANUP(ISC_R_EXISTS);
}
}
dns_qpread_destroy(nodes, &qpr);
/*
* We're about to add a new delegation, check for state of overmem, and
* clean up expired/least recently used delegation, then allocate and
* initialize a new node.
*/
delegdb_node_prepare(delegdb, nodes, now, ttl, zonecut, delegset,
&node);
/*
* Add the node in the DB
*/
dns_qpmulti_write(nodes, &qp);
if (result == ISC_R_SUCCESS) {
/*
* A node at the same zonecut exists, and it is expired. Ignore
* the return value, in case the overriden node would be removed
* in meantime by someone else.
*/
(void)dns_qp_deletename(qp, zonecut, DNS_DBNAMESPACE_NORMAL,
NULL, NULL);
}
result = dns_qp_insert(qp, node, 0);
if (result != ISC_R_SUCCESS) {
/*
* Someone else added the node before (and there was no node to
* delete).
*/
delegdb_node_unref(node);
/*
* Since not using an update (but write) transaction,
* _rollback() won't work here.
*/
dns_qpmulti_commit(nodes, &qp);
CLEANUP(ISC_R_EXISTS);
}
/*
* The new delegation is added, and can be referenced by SIEVE
*/
ISC_SIEVE_INSERT(delegdb->lru[isc_tid()], node, link);
delegdb_node_unref(node);
dns_qp_compact(qp, DNS_QPGC_MAYBE);
dns_qpmulti_commit(nodes, &qp);
cleanup:
rcu_read_unlock();
return result;
}
static void
delegset_destroy(dns_delegset_t *delegset) {
REQUIRE(DNS_DELEGSET_VALID(delegset));
delegset->magic = 0;
ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
deleg->type = DNS_DELEGTYPE_UNDEFINED;
ISC_LIST_UNLINK(delegset->delegs, deleg, link);
ISC_LIST_FOREACH(deleg->addresses, address, link) {
ISC_LIST_UNLINK(deleg->addresses, address, link);
isc_mem_put(delegset->mctx, address, sizeof(*address));
}
ISC_LIST_FOREACH(deleg->names, nameserver, link) {
ISC_LIST_UNLINK(deleg->names, nameserver, link);
dns_name_free(nameserver, delegset->mctx);
isc_mem_put(delegset->mctx, nameserver,
sizeof(*nameserver));
}
isc_mem_put(delegset->mctx, deleg, sizeof(*deleg));
}
isc_mem_putanddetach(&delegset->mctx, delegset, sizeof(*delegset));
}
ISC_REFCOUNT_IMPL(dns_delegset, delegset_destroy);
static void
tostring_namelist(dns_namelist_t *namelist, const char *id, FILE *fp) {
if (!ISC_LIST_EMPTY(*namelist)) {
fprintf(fp, " %s=", id);
ISC_LIST_FOREACH(*namelist, name, link) {
isc_buffer_t nameb;
char bdata[DNS_NAME_MAXWIRE] = { 0 };
isc_buffer_init(&nameb, bdata, sizeof(bdata));
dns_name_totext(name, 0, &nameb);
fprintf(fp, "%s", bdata);
if (name != ISC_LIST_TAIL(*namelist)) {
fprintf(fp, ",");
}
}
}
}
static void
deleg_tostring_addresses(dns_deleg_t *deleg, FILE *fp) {
bool hasv4 = false;
bool hasv6 = false;
ISC_LIST_FOREACH(deleg->addresses, address, link) {
if (address->addr.family == AF_INET) {
hasv4 = true;
} else {
hasv6 = true;
}
}
if (hasv4) {
bool first = true;
fprintf(fp, " server-ipv4=");
ISC_LIST_FOREACH(deleg->addresses, address, link) {
char addrstr[] = "255.255.255.255";
if (address->addr.family == AF_INET6) {
continue;
}
if (!first) {
fprintf(fp, ",");
}
first = false;
inet_ntop(AF_INET, &address->addr.type, addrstr,
sizeof(addrstr));
fprintf(fp, "%s", addrstr);
}
}
if (hasv6) {
bool first = true;
fprintf(fp, " server-ipv6=");
ISC_LIST_FOREACH(deleg->addresses, address, link) {
char addrstr[INET6_ADDRSTRLEN];
if (address->addr.family == AF_INET) {
continue;
}
if (!first) {
fprintf(fp, ",");
}
first = false;
inet_ntop(AF_INET6, &address->addr.type, addrstr,
sizeof(addrstr));
fprintf(fp, "%s", addrstr);
}
}
}
static void
delegset_tostring(const dns_name_t *zonecut, dns_delegset_t *delegset,
isc_stdtime_t now, bool expired, FILE *fp) {
ISC_LIST_FOREACH(delegset->delegs, deleg, link) {
isc_buffer_t zonecutb;
char bdata[DNS_NAME_MAXWIRE];
dns_ttl_t ttl = 0;
if (delegset->expires > now) {
ttl = delegset->expires - now;
} else {
INSIST(expired);
}
isc_buffer_init(&zonecutb, bdata, sizeof(bdata));
dns_name_totext(zonecut, 0, &zonecutb);
fprintf(fp, "%s %u DELEG", bdata, ttl);
if (deleg->type == DNS_DELEGTYPE_DELEG_ADDRESSES ||
deleg->type == DNS_DELEGTYPE_NS_GLUES)
{
deleg_tostring_addresses(deleg, fp);
} else if (deleg->type == DNS_DELEGTYPE_DELEG_NAMES ||
deleg->type == DNS_DELEGTYPE_NS_NAMES)
{
tostring_namelist(&deleg->names, "server-name", fp);
} else if (deleg->type == DNS_DELEGTYPE_DELEG_PARAMS) {
tostring_namelist(&deleg->names, "include-delegparam",
fp);
} else {
UNREACHABLE();
}
fprintf(fp, "\n");
}
}
void
dns_delegdb_dump(dns_delegdb_t *delegdb, bool expired, FILE *fp) {
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(fp != NULL);
dns_qpiter_t it;
dns_qpread_t qpr = {};
delegdb_node_t *node = NULL;
isc_stdtime_t now = isc_stdtime_now();
dns_qpmulti_t *nodes = NULL;
rcu_read_lock();
nodes = rcu_dereference(delegdb->nodes);
if (nodes == NULL) {
rcu_read_unlock();
return;
}
dns_qpmulti_query(nodes, &qpr);
dns_qpiter_init(&qpr, &it);
while (dns_qpiter_next(&it, (void **)&node, NULL) == ISC_R_SUCCESS) {
if (!expired && !isactive(node, now)) {
continue;
}
delegset_tostring(&node->zonecut, node->delegset, now, expired,
fp);
}
dns_qpread_destroy(nodes, &qpr);
rcu_read_unlock();
}
void
dns_delegset_fromnsrdataset(dns_rdataset_t *rdataset,
dns_delegset_t **delegsetp) {
dns_delegset_t *delegset = NULL;
dns_deleg_t *deleg = NULL;
if (rdataset == NULL || !dns_rdataset_isassociated(rdataset) ||
delegsetp == NULL || *delegsetp != NULL)
{
return;
}
REQUIRE(rdataset->type == dns_rdatatype_ns);
delegset = isc_mem_get(isc_g_mctx, sizeof(*delegset));
*delegset = (dns_delegset_t){
.magic = DNS_DELEGSET_MAGIC,
.references = ISC_REFCOUNT_INITIALIZER(1),
.delegs = ISC_LIST_INITIALIZER,
.expires = rdataset->ttl + isc_stdtime_now(),
.staticstub = rdataset->attributes.staticstub
};
isc_mem_attach(isc_g_mctx, &delegset->mctx);
deleg = isc_mem_get(isc_g_mctx, sizeof(*deleg));
*deleg = (dns_deleg_t){ .addresses = ISC_LIST_INITIALIZER,
.names = ISC_LIST_INITIALIZER,
.type = DNS_DELEGTYPE_NS_NAMES,
.link = ISC_LINK_INITIALIZER };
ISC_LIST_APPEND(delegset->delegs, deleg, link);
DNS_RDATASET_FOREACH(rdataset) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_ns_t ns;
dns_rdataset_current(rdataset, &rdata);
dns_rdata_tostruct(&rdata, &ns, NULL);
dns_delegset_addns(delegset, deleg, &ns.name);
}
*delegsetp = delegset;
}
static isc_result_t
deleg_deletetree(dns_qp_t *qp, const dns_name_t *name) {
isc_result_t result;
delegdb_node_t *node = NULL;
dns_qpiter_t it;
ISC_LIST(delegdb_node_t) deadnodes = ISC_LIST_INITIALIZER;
result = dns_qp_lookup(qp, name, DNS_DBNAMESPACE_NORMAL, &it, NULL,
(void **)&node, NULL);
if (result != ISC_R_SUCCESS) {
goto out;
}
INSIST(VALID_DELEGDB_NODE(node));
do {
/*
* Because QP doesn't allow deleting a node while using the
* iterator, the approach is different than `deleg_deletenode()`
* here. Instead of removing the node immediately, we add it
* into a list that we'll go through after, then delete each
* node.
*/
ISC_LIST_APPEND(deadnodes, node, deadlink);
result = dns_qpiter_next(&it, (void **)&node, NULL);
if (result == ISC_R_NOMORE) {
result = ISC_R_SUCCESS;
break;
}
INSIST(VALID_DELEGDB_NODE(node));
if (!dns_name_issubdomain(&node->zonecut, name)) {
break;
}
} while (result == ISC_R_SUCCESS);
out:
if (ISC_LIST_EMPTY(deadnodes)) {
result = ISC_R_NOTFOUND;
} else {
/*
* Let's actually delete the deadnodes!
*/
ISC_LIST_FOREACH(deadnodes, deadnode, deadlink) {
result = dns_qp_deletename(qp, &deadnode->zonecut,
DNS_DBNAMESPACE_NORMAL, NULL,
NULL);
INSIST(result == ISC_R_SUCCESS);
}
}
return result;
}
static isc_result_t
deleg_deletenode(dns_qp_t *qp, const dns_name_t *name) {
return dns_qp_deletename(qp, name, DNS_DBNAMESPACE_NORMAL, NULL, NULL);
}
isc_result_t
dns_delegdb_delete(dns_delegdb_t *delegdb, const dns_name_t *name, bool tree) {
REQUIRE(VALID_DELEGDB(delegdb));
REQUIRE(DNS_NAME_VALID(name));
dns_qpmulti_t *nodes = NULL;
dns_qp_t *qp = NULL;
isc_result_t result = ISC_R_SHUTTINGDOWN;
rcu_read_lock();
nodes = rcu_dereference(delegdb->nodes);
if (nodes != NULL) {
dns_qpmulti_write(nodes, &qp);
if (tree) {
result = deleg_deletetree(qp, name);
} else {
result = deleg_deletenode(qp, name);
}
if (result == ISC_R_SUCCESS) {
dns_qp_compact(qp, DNS_QPGC_MAYBE);
}
dns_qpmulti_commit(nodes, &qp);
}
rcu_read_unlock();
return result;
}
static void
delegdb_shutdown_async(void *arg) {
dns_delegdb_t *delegdb = arg;
REQUIRE(isc_loop_get(isc_tid()) == isc_loop_main());
REQUIRE(delegdb != NULL && VALID_DELEGDB(delegdb));
if (isc_refcount_decrement(&delegdb->owners) == 1) {
dns_qpmulti_t *nodes = rcu_xchg_pointer(&delegdb->nodes, NULL);
if (nodes != NULL) {
nodes_rcu_head_t *nrh = isc_mem_get(delegdb->mctx,
sizeof(*nrh));
*nrh = (nodes_rcu_head_t){
.mctx = isc_mem_ref(delegdb->mctx),
.nodes = nodes,
};
call_rcu(&nrh->rcu_head, deleg_destroy_qpmulti);
}
}
}
void
dns_delegdb_shutdown(dns_delegdb_t *delegdb) {
if (isc_loop_get(isc_tid()) == isc_loop_main()) {
delegdb_shutdown_async(delegdb);
} else {
isc_async_run(isc_loop_main(), delegdb_shutdown_async, delegdb);
}
}
void
dns_delegdb_setsize(dns_delegdb_t *delegdb, size_t size) {
REQUIRE(VALID_DELEGDB(delegdb));
if (size != 0 && size < DELEGDB_MINSIZE) {
size = DELEGDB_MINSIZE;
}
delegdb->hiwater = size - (size >> 3); /* Approximately 7/8ths. */
delegdb->lowater = size - (size >> 2); /* Approximately 3/4ths. */
if (size == 0 || delegdb->hiwater == 0 || delegdb->lowater == 0) {
isc_mem_clearwater(delegdb->mctx);
/*
* TODO: Is it worth a warning if size > 0? Sounds like
* implicit overmem bypass, so the user should be warned...
*/
} else {
isc_mem_setwater(delegdb->mctx, delegdb->hiwater,
delegdb->lowater);
}
}

View file

@ -346,6 +346,18 @@ dns_adb_createfind(dns_adb_t *adb, isc_loop_t *loop, isc_job_cb cb, void *cbarg,
* returns.
*/
void
dns_adb_createaddrinfosfind(dns_adb_t *adb, isc_netaddrlist_t *addrs,
in_port_t port, unsigned int options,
isc_stdtime_t now, size_t maxaddrs,
dns_adbfind_t **findp, size_t *findlen);
/*%<
* Variant of `dns_adb_createfind()` which actually internally looks up
* addresses only using `dns_adb_findaddrinfo()`. This enables the caller to
* abstract the origin of the find (i.e. it needs to be names to be looked up
* into `dns_adbaddrinfo_t`, or from net addrs) and handle it the same way.
*/
void
dns_adb_cancelfind(dns_adbfind_t *find);
/*%<

View file

@ -101,12 +101,6 @@ typedef struct dns_db_methods {
dns_dbversion_t **targetp);
void (*closeversion)(dns_db_t *db, dns_dbversion_t **versionp,
bool commit DNS__DB_FLARG);
isc_result_t (*findzonecut)(dns_db_t *db, const dns_name_t *name,
unsigned int options, isc_stdtime_t now,
dns_dbnode_t **nodep, dns_name_t *foundname,
dns_name_t *dcname,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG);
isc_result_t (*createiterator)(dns_db_t *db, unsigned int options,
dns_dbiterator_t **iteratorp);
isc_result_t (*findrdataset)(dns_db_t *db, dns_dbnode_t *node,
@ -938,9 +932,8 @@ dns__db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
* a zone cut. node, foundname,
* and rdataset reference the
* NS RRset of the zone cut.
* If 'db' is a cache database,
* then this is the deepest known
* delegation.
* This result can only occur
* if 'db' is a zone database.
*
* \li #DNS_R_ZONECUT type == dns_rdatatype_any, and
* the desired node is a zonecut.
@ -969,12 +962,10 @@ dns__db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
* the desired type does not.
*
* \li #ISC_R_NOTFOUND The desired name does not
* exist, and no delegation could
* be found. This result can only
* exist. This result can only
* occur if 'db' is a cache
* database. The caller should
* use its nameserver(s) of last
* resort (e.g. root hints).
* recurse for the data.
*
* \li #DNS_R_NCACHENXDOMAIN The desired name does not
* exist. 'node' is bound to the
@ -1008,61 +999,6 @@ dns__db_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
* errors.
*/
#define dns_db_findzonecut(db, name, options, now, nodep, foundname, dcname, \
rdataset, sigrdataset) \
dns__db_findzonecut(db, name, options, now, nodep, foundname, dcname, \
rdataset, sigrdataset DNS__DB_FILELINE)
isc_result_t
dns__db_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
isc_stdtime_t now, dns_dbnode_t **nodep,
dns_name_t *foundname, dns_name_t *dcname,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG);
/*%<
* Find the deepest known zonecut which encloses 'name' in 'db'.
*
* Notes:
*
* \li If the #DNS_DBFIND_NOEXACT option is set, then the zonecut returned
* (if any) will be the deepest known ancestor of 'name'.
*
* \li If 'now' is zero, then the current time will be used.
*
* Requires:
*
* \li 'db' is a valid database with cache semantics.
*
* \li 'nodep' is NULL, or nodep is a valid pointer and *nodep == NULL.
*
* \li 'foundname' is a valid name with a dedicated buffer.
*
* \li 'dcname' is a valid name with a dedicated buffer.
*
* \li 'rdataset' is NULL, or is a valid unassociated rdataset.
*
* Ensures, on a non-error completion:
*
* \li If nodep != NULL, then it is bound to the found node.
*
* \li If foundname != NULL, then it contains the full name of the
* found node.
*
* \li If dcname != NULL, then it contains the deepest cached name
* that exists in the database.
*
* \li If rdataset != NULL and type != dns_rdatatype_any, then
* rdataset is bound to the found rdataset.
*
* Non-error results are:
*
* \li #ISC_R_SUCCESS
*
* \li #ISC_R_NOTFOUND
*
* \li Other results are possible, and should all be treated as
* errors.
*/
#define dns_db_attachnode(source, targetp) \
dns__db_attachnode(source, targetp DNS__DB_FILELINE)
void

227
lib/dns/include/dns/deleg.h Normal file
View file

@ -0,0 +1,227 @@
/*
* 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.
*/
#pragma once
#include <isc/refcount.h>
#include <isc/stdtime.h>
#include <dns/types.h>
/*
* A `dns_deleg_t` object represents either:
*
* - a DELEG-based delegation with `server-ipv4=` and/or `server-ipv6=`
* (DNS_DELEGTYPE_DELEG_ADDRESS)
*
* - a DELEG-based delegation with `server-name=` (DNS_DELEGTYPE_DELEG_NAMES)
*
* - a DELEG-based delegation with `include-delegparam=`
* (DNS_DELEGTYPE_DELEG_PARAMS)
*
* - an NS-based delegation with glues (DNS_DELEGTYPE_NS_GLUES)
*
* - an NS-based delegation with no glues (DNS_DELEGTYPE_NS_NAMES)
*
* This object must be allocated using `dns_deleg_allocdeleg()`.
*/
typedef enum {
DNS_DELEGTYPE_UNDEFINED,
DNS_DELEGTYPE_DELEG_ADDRESSES,
DNS_DELEGTYPE_DELEG_NAMES,
DNS_DELEGTYPE_DELEG_PARAMS,
DNS_DELEGTYPE_NS_GLUES,
DNS_DELEGTYPE_NS_NAMES
} dns_deleg_type_t;
struct dns_deleg {
isc_netaddrlist_t addresses;
dns_namelist_t names;
dns_deleg_type_t type;
ISC_LINK(dns_deleg_t) link;
};
/*
* A delegation set. Once it's added to the delegation DB, it gets a
* read-only object thus doesn't require any locking nor copying when the
* caller gets it.
*
* The TTL is common to all the DELEG RR for the same zonecut
* https://datatracker.ietf.org/doc/html/rfc2181#section-5.2
*
* When the delegation is NS-based, the TTL is the lowest TTL of the referral
* (either of the NS, A or AAAA glues).
*
* If a zone contains NS and DELEG delegations, this delegation must only
* store the DELEG ones. (This is resolver responsibility to ensure that.)
*/
struct dns_delegset {
unsigned int magic;
isc_mem_t *mctx;
isc_refcount_t references;
dns_deleglist_t delegs;
isc_stdtime_t expires;
/*
* Used only when a delegation is built from a local zone.
*/
bool staticstub;
};
ISC_REFCOUNT_DECL(dns_delegset);
#define DNS_DELEGSET_MAGIC ISC_MAGIC('D', 'e', 'G', 's')
#define DNS_DELEGSET_VALID(delegset) \
ISC_MAGIC_VALID(delegset, DNS_DELEGSET_MAGIC)
typedef struct dns_delegdb dns_delegdb_t;
/*
* Allocate and initialize the delegation database. `db` is attached to the
* caller.
*/
void
dns_delegdb_create(dns_delegdb_t **delegdbp);
/*
* Attach a delegation DB from an existing view to another view. Used when
* reloading the server and the delegation DB is reused.
*/
void
dns_delegdb_reuse(dns_view_t *oldview, dns_view_t *newview);
/*
* Shutdown the delegation database. Must be called from any view shutting down
* which either created a delegdb or reused a delegdb.
*/
void
dns_delegdb_shutdown(dns_delegdb_t *delegdb);
/*
* Lookup for delegations of a given name in the DB. If found, the zonecut is
* written and the delegation set is attached to the caller, so it must be
* detached once the caller is done with it. Even though `delegset` is not
* const (for convenience with ISC_LIST_FOREACH macros, _attach, _detach
* functions, etc.) the `delegset` _is_ a read-only object, and must not be
* modified.
*
* If only the zonecut is needed from the caller, `delegset` can be NULL, it
* won't be attached.
*
* The zonecut must be a initialized and attached to a buffer.
*
* If `now` is 0, the actual expiration time is `isc_stdtime_now()`.
*/
isc_result_t
dns_delegdb_lookup(dns_delegdb_t *db, const dns_name_t *name, isc_stdtime_t now,
unsigned int options, dns_name_t *zonecut,
dns_name_t *deepestzonecut, dns_delegset_t **delegset);
/*
* Allocate and attach to the caller a new empty delegation set, but do not
* attach it in the DB yet, so the following API can be used to set its
* various properties.
*
* Because all those API calls (dns_deleg_alloc* and dns_deleg_add*) use
* the internal delegdb memory context, it _might_ in some circumstances
* allocate above its hiwater mark without reclaiming memory. The flow
* reclaiming memory is then run when adding the delegset into the database
* (dns_deleg_writeanddetach()).
*
* This could be changed to run through those API calls also if needed.
*/
void
dns_delegset_allocset(dns_delegdb_t *db, dns_delegset_t **delegsetp);
/*
* Allocate a new deleg struct and insert it into the delegation set. Can't
* be used on delegation set already attached in the DB.
*/
void
dns_delegset_allocdeleg(dns_delegset_t *delegset, dns_deleg_type_t type,
dns_deleg_t **delegp);
/*
* Add a new IP into a delegation. Can't be used on a delegation from a
* delegation set already attached in the DB.
*/
void
dns_delegset_addaddr(dns_delegset_t *delegset, dns_deleg_t *deleg,
const isc_netaddr_t *addr);
/*
* Add a new DELEGPARAM name into a delegation. Can't be used on a delegation
* from a delegation set already attached in the DB.
*/
void
dns_delegset_adddelegparam(dns_delegset_t *delegset, dns_deleg_t *deleg,
const dns_name_t *name);
/*
* Add a new nameserver name into a delegation. Can't be used on a delegation
* from a delegation set already attached in the DB.
*/
void
dns_delegset_addns(dns_delegset_t *delegset, dns_deleg_t *deleg,
const dns_name_t *name);
/*
* Add a delegation set into the DB for the given zonecut and a time to live. If
* a delegation already exists and is not expired, ISC_R_EXISTS is returned and
* the DB is not altered.
*
* This function also cleanup least recently used delegation is the database in
* an overmemory conditions (See dns_deleg_setsize()).
*
* TODO: once DELEG is supported, attempting to add a delegation from NS
* where a delegation from DELEG already exists would be rejected too.
*/
isc_result_t
dns_delegset_insert(dns_delegdb_t *db, const dns_name_t *zonecut, dns_ttl_t ttl,
dns_delegset_t *delegset);
/*
* Dump the database in a textual format for a given name. If `expired` is
* false, only the non expired entries are shown. All entries are shown
* otherwise.
*/
void
dns_delegdb_dump(dns_delegdb_t *db, bool expired, FILE *fp);
/*
* Convert an NS rdataset into a delegset containing a single delegation
* (with possibly multiple nameserver). The allocated delegset is using the
* main memory context, thus, is not expected to be added into the deleg DB
* (which accepts only delegset allocated using `dns_deleg_alloc*()` APIs.
*/
void
dns_delegset_fromnsrdataset(dns_rdataset_t *rdataset,
dns_delegset_t **delegsetp);
/*
* Delete a delegation matching a name. If `tree` is true, this will also
* delete all names below `name`.
*/
isc_result_t
dns_delegdb_delete(dns_delegdb_t *db, const dns_name_t *name, bool tree);
/*
* Defines the size of the delegation cache. Whenever the effective cache
* size comes close to this size, least recently used cache entries are
* discarded. Value `0` means there is no limitation.
*/
void
dns_delegdb_setsize(dns_delegdb_t *db, size_t size);
ISC_REFCOUNT_DECL(dns_delegdb);

View file

@ -262,8 +262,7 @@ typedef struct fetchctx fetchctx_t;
isc_result_t
dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
dns_rdataset_t *nameservers,
dns_forwarders_t *forwarders,
dns_delegset_t *delegset, dns_forwarders_t *forwarders,
const isc_sockaddr_t *client, dns_messageid_t id,
unsigned int options, unsigned int depth,
isc_counter_t *qc, isc_counter_t *gqc,

View file

@ -178,6 +178,9 @@ typedef ISC_LIST(dns_zone_t) dns_zonelist_t;
typedef struct dns_zonemgr dns_zonemgr_t;
typedef struct dns_zt dns_zt_t;
typedef struct dns_ipkeylist dns_ipkeylist_t;
typedef struct dns_deleg dns_deleg_t;
typedef ISC_LIST(dns_deleg_t) dns_deleglist_t;
typedef struct dns_delegset dns_delegset_t;
typedef struct dst_gssapi_signverifyctx dst_gssapi_signverifyctx_t;

View file

@ -67,6 +67,7 @@
#include <dns/acl.h>
#include <dns/catz.h>
#include <dns/clientinfo.h>
#include <dns/deleg.h>
#include <dns/dnstap.h>
#include <dns/fixedname.h>
#include <dns/nta.h>
@ -98,6 +99,7 @@ struct dns_view {
dns_cache_t *cache;
dns_db_t *cachedb;
dns_db_t *hints;
dns_delegdb_t *deleg;
/*
* security roots and negative trust anchors.
@ -692,7 +694,7 @@ isc_result_t
dns_view_bestzonecut(dns_view_t *view, const dns_name_t *name,
dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now,
unsigned int options, bool use_hints, bool use_cache,
dns_rdataset_t *rdataset);
dns_delegset_t **delegsetp);
/*%<
* Find the best known zonecut containing 'name'.
*
@ -720,15 +722,16 @@ dns_view_bestzonecut(dns_view_t *view, const dns_name_t *name,
*
*\li 'name' is valid name.
*
*\li 'rdataset' is a valid, disassociated rdataset.
*
*\li 'sigrdataset' is NULL, or is a valid, disassociated rdataset.
*\li 'delegsetp' is a valid pointer to a NULL `dns_delegset_t` pointer. It
* can also be NULL if the caller doesn't need the delegation data (but
* just the zonecut name).
* Note: if a delegation is found, `*delegsetp` is not NULL and must be
* detached from the caller once it doesn't need it anymore.
*
* Returns:
*
*\li #ISC_R_SUCCESS If a delegation is found;
*\li #DNS_R_NXDOMAIN If no delegation is found; 'rdataset' and 'sigrdataset'
* are disassociated.
*\li #DNS_R_NXDOMAIN If no delegation is found; '*delegsetp' remains NULL.
*/
isc_result_t

View file

@ -89,6 +89,7 @@ dns_srcset.add(
'compress.c',
'db.c',
'dbiterator.c',
'deleg.c',
'diff.c',
'dispatch.c',
'dlz.c',

View file

@ -1364,7 +1364,7 @@ find_headers(qpcnode_t *node, qpc_search_t *search, dns_rdatatype_t type,
}
static isc_result_t
check_zonecut(qpcnode_t *node, void *arg DNS__DB_FLARG) {
check_dname(qpcnode_t *node, void *arg DNS__DB_FLARG) {
qpc_search_t *search = arg;
dns_slabheader_t *found = NULL, *foundsig = NULL;
isc_result_t result;
@ -1404,66 +1404,6 @@ check_zonecut(qpcnode_t *node, void *arg DNS__DB_FLARG) {
return result;
}
static isc_result_t
find_deepest_zonecut(qpc_search_t *search, qpcnode_t *node,
dns_dbnode_t **nodep, dns_name_t *foundname,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
isc_result_t result = ISC_R_NOTFOUND;
qpcache_t *qpdb = NULL;
/*
* Caller must be holding the tree lock.
*/
qpdb = search->qpdb;
for (int i = dns_qpchain_length(&search->chain) - 1; i >= 0; i--) {
dns_slabheader_t *found = NULL, *foundsig = NULL;
isc_rwlock_t *nlock = NULL;
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
dns_qpchain_node(&search->chain, i, (void **)&node, NULL);
nlock = &qpdb->buckets[node->locknum].lock;
NODE_RDLOCK(nlock, &nlocktype);
/*
* Look for NS and RRSIG NS rdatasets.
*/
find_headers(node, search, dns_rdatatype_ns, &found, &foundsig);
if (found != NULL) {
/*
* If we have to set foundname, we do it before
* anything else.
*/
if (foundname != NULL) {
dns_name_copy(&node->name, foundname);
}
result = DNS_R_DELEGATION;
if (nodep != NULL) {
qpcnode_acquire(
search->qpdb, node, nlocktype,
isc_rwlocktype_none DNS__DB_FLARG_PASS);
*nodep = (dns_dbnode_t *)node;
}
bindrdatasets(search->qpdb, node, found, foundsig,
search->now, nlocktype,
isc_rwlocktype_none, rdataset,
sigrdataset DNS__DB_FLARG_PASS);
}
NODE_UNLOCK(nlock, &nlocktype);
if (found != NULL) {
break;
}
}
return result;
}
/*
* Look for a potentially covering NSEC in the cache where `name`
* is known not to exist. This uses the auxiliary NSEC tree to find
@ -1594,14 +1534,13 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
bool cname_ok = true;
bool found_noqname = false;
bool all_negative = true;
bool empty_node;
bool empty_node = true;
isc_rwlock_t *nlock = NULL;
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
dns_slabheader_t *found = NULL, *foundsig = NULL;
dns_slabheader_t *nsheader = NULL, *nssig = NULL;
dns_slabheader_t *nsecheader = NULL, *nsecsig = NULL;
dns_typepair_t typepair;
dns_typepair_t typepair = DNS_TYPEPAIR(type);
if (type == dns_rdatatype_none) {
/* We can't search negative cache directly */
@ -1626,8 +1565,8 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
}
/*
* Check the QP chain to see if there's a node above us with a
* active DNAME or NS rdatasets.
* Check the QP chain to see if there's a node above us with an
* active DNAME rdataset.
*
* We're only interested in nodes above QNAME, so if the result
* was success, then we skip the last item in the chain.
@ -1638,14 +1577,14 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
}
for (unsigned int i = 0; i < len; i++) {
isc_result_t zcresult;
isc_result_t tresult;
qpcnode_t *encloser = NULL;
dns_qpchain_node(&search.chain, i, (void **)&encloser, NULL);
zcresult = check_zonecut(encloser,
(void *)&search DNS__DB_FLARG_PASS);
if (zcresult != DNS_R_CONTINUE) {
tresult = check_dname(encloser,
(void *)&search DNS__DB_FLARG_PASS);
if (tresult != DNS_R_CONTINUE) {
result = DNS_R_PARTIALMATCH;
search.chain.len = i - 1;
node = encloser;
@ -1678,10 +1617,7 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
tlocktype DNS__DB_FLARG_PASS);
goto tree_exit;
} else {
find_ns:
result = find_deepest_zonecut(
&search, node, nodep, foundname, rdataset,
sigrdataset DNS__DB_FLARG_PASS);
result = ISC_R_NOTFOUND;
goto tree_exit;
}
} else if (result != ISC_R_SUCCESS) {
@ -1705,19 +1641,6 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
nlock = &search.qpdb->buckets[node->locknum].lock;
NODE_RDLOCK(nlock, &nlocktype);
/*
* These pointers need to be reset here in case we did
* 'goto find_ns' from somewhere below.
*/
found = NULL;
foundsig = NULL;
typepair = DNS_TYPEPAIR(type);
nsheader = NULL;
nsecheader = NULL;
nssig = NULL;
nsecsig = NULL;
empty_node = true;
DNS_SLABTOP_FOREACH(top, node->data) {
dns_slabheader_t *header = NULL, *sigheader = NULL;
if (DNS_TYPEPAIR_TYPE(top->typepair) == dns_rdatatype_rrsig) {
@ -1799,12 +1722,6 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
}
break;
case dns_rdatatype_ns:
case DNS_SIGTYPEPAIR(dns_rdatatype_ns):
nsheader = header;
nssig = sigheader;
break;
case dns_rdatatype_nsec:
case DNS_SIGTYPEPAIR(dns_rdatatype_nsec):
nsecheader = header;
@ -1839,7 +1756,9 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
goto tree_exit;
}
}
goto find_ns;
result = ISC_R_NOTFOUND;
goto tree_exit;
}
/*
@ -1874,34 +1793,14 @@ qpcache_find(dns_db_t *db, const dns_name_t *name, dns_dbversion_t *version,
result = find_coveringnsec(
&search, name, nodep, foundname, rdataset,
sigrdataset DNS__DB_FLARG_PASS);
if (result == DNS_R_COVERINGNSEC) {
goto tree_exit;
if (result != DNS_R_COVERINGNSEC) {
result = ISC_R_NOTFOUND;
}
goto find_ns;
goto tree_exit;
}
/*
* If there is an NS rdataset at this node, then this is the
* deepest zone cut.
*/
if (nsheader != NULL) {
if (nodep != NULL) {
qpcnode_acquire(search.qpdb, node, nlocktype,
tlocktype DNS__DB_FLARG_PASS);
*nodep = (dns_dbnode_t *)node;
}
bindrdatasets(search.qpdb, node, nsheader, nssig,
search.now, nlocktype, tlocktype,
rdataset, sigrdataset DNS__DB_FLARG_PASS);
result = DNS_R_DELEGATION;
goto node_exit;
}
/*
* Go find the deepest zone cut.
*/
NODE_UNLOCK(nlock, &nlocktype);
goto find_ns;
result = ISC_R_NOTFOUND;
goto node_exit;
}
/*
@ -1974,131 +1873,6 @@ tree_exit:
return result;
}
static isc_result_t
seek_ns_headers(qpc_search_t *search, qpcnode_t *node, dns_dbnode_t **nodep,
dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset,
dns_name_t *foundname, dns_name_t *dcname,
isc_rwlocktype_t *tlocktype) {
isc_rwlocktype_t nlocktype = isc_rwlocktype_none;
isc_rwlock_t *nlock = &search->qpdb->buckets[node->locknum].lock;
dns_slabheader_t *found = NULL, *foundsig = NULL;
NODE_RDLOCK(nlock, &nlocktype);
find_headers(node, search, dns_rdatatype_ns, &found, &foundsig);
if (found == NULL) {
isc_result_t result;
/*
* No active NS records found. Call find_deepest_zonecut()
* to look for them in nodes above this one.
*/
NODE_UNLOCK(nlock, &nlocktype);
result = find_deepest_zonecut(search, node, nodep, foundname,
rdataset,
sigrdataset DNS__DB_FLARG_PASS);
if (dcname != NULL) {
dns_name_copy(foundname, dcname);
}
return result;
}
if (nodep != NULL) {
qpcnode_acquire(search->qpdb, node, nlocktype,
*tlocktype DNS__DB_FLARG_PASS);
*nodep = (dns_dbnode_t *)node;
}
bindrdatasets(search->qpdb, node, found, foundsig, search->now,
nlocktype, *tlocktype, rdataset,
sigrdataset DNS__DB_FLARG_PASS);
NODE_UNLOCK(nlock, &nlocktype);
return ISC_R_SUCCESS;
}
static isc_result_t
qpcache_findzonecut(dns_db_t *db, const dns_name_t *name, unsigned int options,
isc_stdtime_t __now, dns_dbnode_t **nodep,
dns_name_t *foundname, dns_name_t *dcname,
dns_rdataset_t *rdataset,
dns_rdataset_t *sigrdataset DNS__DB_FLARG) {
qpcnode_t *node = NULL;
isc_result_t result;
isc_rwlocktype_t tlocktype = isc_rwlocktype_none;
qpc_search_t search = (qpc_search_t){
.qpdb = (qpcache_t *)db,
.options = options,
.now = __now ? __now : isc_stdtime_now(),
};
unsigned int len = 0;
REQUIRE(VALID_QPDB((qpcache_t *)db));
TREE_RDLOCK(&search.qpdb->tree_lock, &tlocktype);
/*
* Search down from the root of the tree.
*/
result = dns_qp_lookup(search.qpdb->tree, name, DNS_DBNAMESPACE_NORMAL,
NULL, &search.chain, (void **)&node, NULL);
switch (result) {
case ISC_R_SUCCESS:
if ((options & DNS_DBFIND_NOEXACT) == 0) {
if (dcname != NULL) {
dns_name_copy(&node->name, dcname);
}
dns_name_copy(&node->name, foundname);
result = seek_ns_headers(&search, node, nodep, rdataset,
sigrdataset, foundname, dcname,
&tlocktype);
break;
}
len = dns_qpchain_length(&search.chain);
if (len < 2) {
result = ISC_R_NOTFOUND;
break;
}
FALLTHROUGH;
case DNS_R_PARTIALMATCH:
if (dcname != NULL) {
dns_name_copy(&node->name, dcname);
}
if (result == ISC_R_SUCCESS) {
/* Fell through from the previous case */
INSIST(len >= 2);
node = NULL;
dns_qpchain_node(&search.chain, len - 2, (void **)&node,
NULL);
search.chain.len = len - 1;
}
result = find_deepest_zonecut(&search, node, nodep, foundname,
rdataset,
sigrdataset DNS__DB_FLARG_PASS);
break;
default:
break;
}
TREE_UNLOCK(&search.qpdb->tree_lock, &tlocktype);
INSIST(!search.need_cleanup);
if (result == DNS_R_DELEGATION) {
result = ISC_R_SUCCESS;
}
return result;
}
static isc_result_t
qpcache_findrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
dns_rdatatype_t type, dns_rdatatype_t covers,
@ -3852,7 +3626,6 @@ static dns_dbmethods_t qpdb_cachemethods = {
.destroy = qpcache_destroy,
.findnode = qpcache_findnode,
.find = qpcache_find,
.findzonecut = qpcache_findzonecut,
.createiterator = qpcache_createiterator,
.findrdataset = qpcache_findrdataset,
.allrdatasets = qpcache_allrdatasets,

View file

@ -47,6 +47,7 @@
#include <dns/adb.h>
#include <dns/cache.h>
#include <dns/db.h>
#include <dns/deleg.h>
#include <dns/dispatch.h>
#include <dns/dns64.h>
#include <dns/dnstap.h>
@ -358,7 +359,7 @@ struct fetchctx {
/*% Locked by loop event serialization. */
dns_fixedname_t dfname;
dns_name_t *domain;
dns_rdataset_t nameservers;
dns_delegset_t *delegset;
atomic_uint_fast32_t attributes;
isc_timer_t *timer;
isc_time_t expires;
@ -509,18 +510,17 @@ struct fetchctx {
#define FCTX_MAGIC ISC_MAGIC('F', '!', '!', '!')
#define VALID_FCTX(fctx) ISC_MAGIC_VALID(fctx, FCTX_MAGIC)
#define FCTX_ATTR_HAVEANSWER 0x0001
#define FCTX_ATTR_GLUING 0x0002
#define FCTX_ATTR_ADDRWAIT 0x0004
#define FCTX_ATTR_WANTCACHE 0x0010
#define FCTX_ATTR_WANTNCACHE 0x0020
#define FCTX_ATTR_TRIEDFIND 0x0080
#define FCTX_ATTR_TRIEDALT 0x0100
enum {
FCTX_ATTR_HAVEANSWER = 1 << 0,
FCTX_ATTR_ADDRWAIT = 1 << 1,
FCTX_ATTR_WANTCACHE = 1 << 2,
FCTX_ATTR_WANTNCACHE = 1 << 3,
FCTX_ATTR_TRIEDFIND = 1 << 4,
FCTX_ATTR_TRIEDALT = 1 << 5,
};
#define HAVE_ANSWER(f) \
((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_HAVEANSWER) != 0)
#define GLUING(f) \
((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_GLUING) != 0)
#define ADDRWAIT(f) \
((atomic_load_acquire(&(f)->attributes) & FCTX_ATTR_ADDRWAIT) != 0)
#define SHUTTINGDOWN(f) ((f)->state == fetchstate_done)
@ -649,9 +649,8 @@ enum {
#define BADCOOKIE(a) (((a)->flags & FCTX_ADDRINFO_BADCOOKIE) != 0)
#define ISDUALSTACK(a) (((a)->flags & FCTX_ADDRINFO_DUALSTACK) != 0)
#define NXDOMAIN(r) (((r)->attributes.nxdomain))
#define NEGATIVE(r) (((r)->attributes.negative))
#define STATICSTUB(r) (((r)->attributes.staticstub))
#define NXDOMAIN(r) (((r)->attributes.nxdomain))
#define NEGATIVE(r) (((r)->attributes.negative))
#ifdef ENABLE_AFL
bool dns_fuzzing_resolver = false;
@ -838,7 +837,7 @@ resume_qmin(void *arg);
static isc_result_t
get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
dns_delegset_t *delegset, const isc_sockaddr_t *client,
unsigned int options, unsigned int depth, isc_counter_t *qc,
isc_counter_t *gqc, fetchctx_t *parent, fetchctx_t **fctxp,
bool *new_fctx);
@ -3654,61 +3653,130 @@ fctx_getaddresses_forwarders(fetchctx_t *fctx) {
return DNS_R_CONTINUE;
}
static void
fctx_getaddresses_addresses(fetchctx_t *fctx, isc_stdtime_t now,
unsigned int options, bool *allspilledp,
size_t *ns_processed) {
dns_adbfindlist_t finds = ISC_LIST_INITIALIZER;
size_t max_delegation_servers = fctx->res->view->max_delegation_servers;
if ((fctx->options & DNS_FETCHOPT_PREFETCH) != 0) {
options |= DNS_ADBFIND_QUOTAEXEMPT;
}
ISC_LIST_FOREACH(fctx->delegset->delegs, deleg, link) {
dns_adbfind_t *find = NULL;
size_t maxaddrs = max_delegation_servers - *ns_processed;
size_t findlen = 0;
if (*ns_processed >= max_delegation_servers) {
break;
}
if (deleg->type != DNS_DELEGTYPE_DELEG_ADDRESSES &&
deleg->type != DNS_DELEGTYPE_NS_GLUES)
{
continue;
}
if (ISC_LIST_EMPTY(deleg->addresses)) {
continue;
}
fetchctx_ref(fctx);
dns_adb_createaddrinfosfind(fctx->adb, &deleg->addresses,
fctx->res->view->dstport, options,
now, maxaddrs, &find, &findlen);
if (find == NULL) {
fetchctx_unref(fctx);
break;
}
if ((find->options & DNS_ADBFIND_OVERQUOTA) != 0) {
*allspilledp = true;
fctx->quotacount++;
}
if (ISC_LIST_EMPTY(find->list)) {
fetchctx_unref(fctx);
dns_adb_destroyfind(&find);
break;
}
*ns_processed += findlen;
INSIST(*ns_processed <= max_delegation_servers);
ISC_LIST_APPEND(finds, find, publink);
}
if (!ISC_LIST_EMPTY(finds)) {
ISC_LIST_APPENDLIST(fctx->finds, finds, publink);
}
}
static isc_result_t
fctx_getaddresses_nameservers(fetchctx_t *fctx, isc_stdtime_t now,
unsigned int stdoptions, size_t fetches_allowed,
bool *need_alternatep, bool *all_spilledp) {
dns_rdata_ns_t ns;
bool *need_alternatep, bool *all_spilledp,
size_t *ns_processed) {
bool have_address = false;
unsigned int ns_processed = 0;
uint32_t ns_processing_limit = fctx->res->view->max_delegation_servers;
static thread_local dns_rdata_t nameservers_s[MAX_DELEGATION_SERVERS];
static thread_local dns_rdata_t *nameservers[MAX_DELEGATION_SERVERS];
unsigned int name_processed = 0;
static thread_local dns_name_t *nameservers[MAX_DELEGATION_SERVERS];
size_t max_delegation_servers = fctx->res->view->max_delegation_servers;
DNS_RDATASET_FOREACH(&fctx->nameservers) {
dns_rdata_t *rdata = nameservers[ns_processed] =
&nameservers_s[ns_processed];
/*
* Lookup through each delegation for this zonecut (represented by
* `delegset`).
*
* If this is an NS-based delegation, each `deleg` represents an NS RR
* and will have a single server name.
*
* If this is a DELEG-based delegation, each `deleg` represents a DELEG
* RR and might have multiple server names.
*/
ISC_LIST_FOREACH(fctx->delegset->delegs, deleg, link) {
if (deleg->type != DNS_DELEGTYPE_DELEG_NAMES &&
deleg->type != DNS_DELEGTYPE_NS_NAMES)
{
continue;
}
dns_rdata_init(rdata);
if (ISC_LIST_EMPTY(deleg->names)) {
continue;
}
dns_rdataset_current(&fctx->nameservers, rdata);
ISC_LIST_FOREACH(deleg->names, ns, link) {
nameservers[name_processed++] = ns;
if (++ns_processed >= ns_processing_limit) {
break;
if (name_processed >= max_delegation_servers) {
goto shufflens;
}
}
}
if (ns_processed > 1 && ns_processed > fetches_allowed) {
shufflens:
if (name_processed > 1 && name_processed > fetches_allowed) {
/*
* Skip the shuffle if:
* - there's nothing to shuffle (no or one nameserver)
* - there are less nameserver than allowed fetches as
* we are going to start fetches for all of them.
*/
for (size_t i = 0; i < ns_processed - 1; i++) {
size_t j = i + isc_random_uniform(ns_processed - i);
for (size_t i = 0; i < name_processed - 1; i++) {
size_t j = i + isc_random_uniform(name_processed - i);
ISC_SWAP(nameservers[i], nameservers[j]);
}
}
for (size_t i = 0; i < ns_processed; i++) {
isc_result_t result = ISC_R_SUCCESS;
for (size_t i = 0; i < name_processed; i++) {
bool overquota = false;
unsigned int static_stub = 0;
unsigned int no_fetch = 0;
dns_rdata_t *rdata = nameservers[i];
dns_name_t *ns = nameservers[i];
/*
* Extract the name from the NS record.
*/
result = dns_rdata_tostruct(rdata, &ns, NULL);
if (result != ISC_R_SUCCESS) {
continue;
}
if (STATICSTUB(&fctx->nameservers) &&
dns_name_equal(&ns.name, fctx->domain))
if (fctx->delegset->staticstub &&
dns_name_equal(ns, fctx->domain))
{
static_stub = DNS_ADBFIND_STATICSTUB;
}
@ -3721,14 +3789,16 @@ fctx_getaddresses_nameservers(fetchctx_t *fctx, isc_stdtime_t now,
no_fetch = DNS_ADBFIND_NOFETCH;
}
findname(fctx, &ns.name, 0, stdoptions | static_stub | no_fetch,
0, now, &overquota, need_alternatep, &have_address);
findname(fctx, ns, 0, stdoptions | static_stub | no_fetch, 0,
now, &overquota, need_alternatep, &have_address);
if (!overquota) {
*all_spilledp = false;
}
dns_rdata_freestruct(&ns);
if (++(*ns_processed) >= max_delegation_servers) {
break;
}
}
if (fctx->pending_running == 0 && !have_address) {
@ -3791,6 +3861,7 @@ fctx_getaddresses(fetchctx_t *fctx) {
bool need_alternate = false;
bool all_spilled = false;
size_t fetches_allowed = 0;
size_t ns_processed = 0;
FCTXTRACE5("getaddresses", "fctx->depth=", fctx->depth);
@ -3876,25 +3947,45 @@ fctx_getaddresses(fetchctx_t *fctx) {
}
now = isc_stdtime_now();
all_spilled = true; /* resets to false below after the first success */
INSIST(ISC_LIST_EMPTY(fctx->finds));
INSIST(ISC_LIST_EMPTY(fctx->altfinds));
/*
* A dns_delegset_t can only have either
*
* - addresses (either from DELEG-based delegation with only addresses,
* or NS-based delegation with glues)
* - name servers to lookup (either from DELEG-based delegation with
* only name servers, or NS-based delegation without glues)
* - include-delegparam (from DELEG-based delegation only -- NYI).
*
* So let's try in this order. If nothing's found, then we can attempt
* alternates.
*
* Either way, the maximum number of nameserver names and addresses used
* for this resolution is at most `max_delegation_servers`. This is why
* `ns_processed` is shared with `fctx_getaddresses_addresses` and
* `fctx_getaddresses_nameservers`.
* */
fctx_getaddresses_addresses(fctx, now, stdoptions, &all_spilled,
&ns_processed);
fetches_allowed = fctx_getaddresses_allowed(fctx);
result = fctx_getaddresses_nameservers(fctx, now, stdoptions,
fetches_allowed, &need_alternate,
&all_spilled);
&all_spilled, &ns_processed);
if (result == DNS_R_CONTINUE && fetches_allowed == 0) {
/*
* We have no addresses and we haven't allowed any
* fetches to be started. Allow one extra fetch and try
* again.
*/
(void)fctx_getaddresses_nameservers(fctx, now, stdoptions, 1,
&need_alternate,
&all_spilled);
(void)fctx_getaddresses_nameservers(
fctx, now, stdoptions, 1, &need_alternate, &all_spilled,
&ns_processed);
}
/*
@ -4318,7 +4409,7 @@ fctx_try(fetchctx_t *fctx, bool retrying) {
fetchctx_ref(fctx);
result = dns_resolver_createfetch(
fctx->res, fctx->qmin.name, fctx->qmintype,
fctx->domain, &fctx->nameservers, NULL, NULL, 0,
fctx->domain, fctx->delegset, NULL, NULL, 0,
options | DNS_FETCHOPT_QMINFETCH, 0, fctx->qc,
fctx->gqc, fctx, fctx->loop, resume_qmin, fctx,
&fctx->edectx, &fctx->qmin.rdataset,
@ -4531,14 +4622,14 @@ resume_qmin(void *arg) {
}
clear_resp(&resp);
dns_rdataset_cleanup(&fctx->nameservers);
dns_delegset_detach(&fctx->delegset);
if (dns_rdatatype_atparent(fctx->type)) {
findoptions |= DNS_DBFIND_NOEXACT;
}
result = dns_view_bestzonecut(res->view, fctx->name, fname, dcname,
fctx->now, findoptions, true, true,
&fctx->nameservers);
&fctx->delegset);
FCTXTRACEN("resume_qmin findzonecut", fname, result);
if (result != ISC_R_SUCCESS) {
@ -4551,7 +4642,7 @@ resume_qmin(void *arg) {
CHECK(fcount_incr(fctx, false));
dns_name_copy(dcname, fctx->qmin.dcname);
fctx->ns_ttl = fctx->nameservers.ttl;
fctx->ns_ttl = fctx->delegset->expires - fctx->now;
fctx->ns_ttl_ok = true;
fctx_minimize_qname(fctx);
@ -4648,7 +4739,9 @@ fctx__destroy(fetchctx_t *fctx, const char *func, const char *file,
}
fcount_decr(fctx);
dns_message_detach(&fctx->qmessage);
dns_rdataset_cleanup(&fctx->nameservers);
if (fctx->delegset != NULL) {
dns_delegset_detach(&fctx->delegset);
}
dns_db_detach(&fctx->cache);
dns_adb_detach(&fctx->adb);
dns_dispatchmgr_detach(&fctx->dispatchmgr);
@ -4799,7 +4892,7 @@ log_ns_ttl(fetchctx_t *fctx, const char *where) {
static isc_result_t
fctx__create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
dns_delegset_t *delegset, const isc_sockaddr_t *client,
unsigned int options, unsigned int depth, isc_counter_t *qc,
isc_counter_t *gqc, fetchctx_t *parent, fetchctx_t **fctxp,
const char *func, const char *file, const unsigned int line) {
@ -4839,7 +4932,6 @@ fctx__create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
.bad = ISC_LIST_INITIALIZER,
.edns = ISC_LIST_INITIALIZER,
.validators = ISC_LIST_INITIALIZER,
.nameservers = DNS_RDATASET_INIT,
.nsrrset = DNS_RDATASET_INIT,
.resp_result = DNS_R_SERVFAIL,
.qmin.rdataset = DNS_RDATASET_INIT,
@ -4976,21 +5068,21 @@ fctx__create(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
result = dns_view_bestzonecut(
res->view, name, fctx->fwdname, dcname,
fctx->now, findoptions, true, true,
&fctx->nameservers);
&fctx->delegset);
if (result != ISC_R_SUCCESS) {
goto cleanup_nameservers;
}
dns_name_copy(fctx->fwdname, fctx->domain);
dns_name_copy(dcname, fctx->qmin.dcname);
fctx->ns_ttl = fctx->nameservers.ttl;
fctx->ns_ttl = fctx->delegset->expires - fctx->now;
fctx->ns_ttl_ok = true;
}
} else {
dns_rdataset_clone(nameservers, &fctx->nameservers);
dns_delegset_attach(delegset, &fctx->delegset);
dns_name_copy(domain, fctx->domain);
dns_name_copy(domain, fctx->qmin.dcname);
fctx->ns_ttl = fctx->nameservers.ttl;
fctx->ns_ttl = fctx->delegset->expires - fctx->now;
fctx->ns_ttl_ok = true;
}
@ -5105,7 +5197,9 @@ cleanup_fcount:
fcount_decr(fctx);
cleanup_nameservers:
dns_rdataset_cleanup(&fctx->nameservers);
if (fctx->delegset != NULL) {
dns_delegset_detach(&fctx->delegset);
}
isc_mem_free(fctx->mctx, fctx->info);
if (fctx->nfails != NULL) {
isc_counter_detach(&fctx->nfails);
@ -6235,15 +6329,13 @@ cleanup:
static isc_result_t
rctx_cachemessage(respctx_t *rctx) {
isc_result_t result;
isc_result_t result = ISC_R_SUCCESS;
fetchctx_t *fctx = rctx->fctx;
resquery_t *query = rctx->query;
dns_message_t *message = query->rmessage;
FCTXTRACE("rctx_cachemessage");
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
LOCK(&fctx->lock);
for (dns_section_t section = DNS_SECTION_ANSWER;
@ -6256,6 +6348,8 @@ rctx_cachemessage(respctx_t *rctx) {
}
}
FCTX_ATTR_CLR(fctx, FCTX_ATTR_WANTCACHE);
cleanup:
UNLOCK(&fctx->lock);
return result;
@ -6422,21 +6516,9 @@ done:
}
static void
mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external,
bool gluing) {
mark_related(dns_name_t *name, dns_rdataset_t *rdataset, bool external) {
name->attributes.cache = true;
if (gluing) {
rdataset->trust = dns_trust_glue;
/*
* Glue with 0 TTL causes problems. We force the TTL to
* 1 second to prevent this.
*/
if (rdataset->ttl == 0) {
rdataset->ttl = 1;
}
} else {
rdataset->trust = dns_trust_additional;
}
rdataset->trust = dns_trust_additional;
/*
* Avoid infinite loops by only marking new rdatasets.
@ -6547,6 +6629,120 @@ name_external(const dns_name_t *name, dns_rdatatype_t type, respctx_t *rctx) {
return false;
}
static void
cache_delegglue(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
dns_rdataset_t *rdataset) {
if (rdataset->ttl < *ttl) {
*ttl = rdataset->ttl;
}
DNS_RDATASET_FOREACH(rdataset) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_in_a_t a;
isc_netaddr_t addr = { .family = AF_INET };
dns_rdataset_current(rdataset, &rdata);
dns_rdata_tostruct(&rdata, &a, NULL);
addr.type.in = a.in_addr;
dns_delegset_addaddr(delegset, deleg, &addr);
}
}
static void
cache_delegglue6(dns_delegset_t *delegset, dns_deleg_t *deleg, dns_ttl_t *ttl,
dns_rdataset_t *rdataset) {
if (rdataset->ttl < *ttl) {
*ttl = rdataset->ttl;
}
DNS_RDATASET_FOREACH(rdataset) {
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_in_aaaa_t aaaa;
isc_netaddr_t addr = { .family = AF_INET6 };
dns_rdataset_current(rdataset, &rdata);
dns_rdata_tostruct(&rdata, &aaaa, NULL);
addr.type.in6 = aaaa.in6_addr;
dns_delegset_addaddr(delegset, deleg, &addr);
}
}
/*
* Cache the parent-side NS RRset in a delegation.
*
* Currently the resolver doesn't support DELEG, but when it does, this
* code will need to bail out if there is already a delegset from DELEG
* RRset in this zonecut. (See DELEG draft 5.1.3.)
*
* Maybe the simplest way to enforce it could be to pass a boolean flag
* `nooverride` to `dns_deleg_writeset()` so it simply detaches the
* `delegset` if there is already a `delegset` at this zonecut in the DB.
* And the flag would be true only from `cache_delegns()`.
*/
static isc_result_t
cache_delegns(respctx_t *rctx) {
fetchctx_t *fctx = rctx->fctx;
dns_delegdb_t *delegdb = fctx->res->view->deleg;
dns_delegset_t *delegset = NULL;
dns_ttl_t ttl = rctx->ns_rdataset->ttl;
isc_result_t result;
FCTXTRACE("cache_delegns");
dns_delegset_allocset(delegdb, &delegset);
DNS_RDATASET_FOREACH(rctx->ns_rdataset) {
dns_rdataset_t *gluerdataset = NULL;
dns_rdata_t rdata = DNS_RDATA_INIT;
dns_rdata_ns_t ns;
dns_deleg_t *deleg = NULL;
/*
* We can't "group" all NS-based delegations into a single
* `dns_deleg_t` because some of them might have glues, some
* other might not, and a `dns_deleg_t` can't have both
* addresses and NS names. Let's assume this is a GLUE-based
* deleg first.
*/
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES,
&deleg);
dns_rdataset_current(rctx->ns_rdataset, &rdata);
INSIST(rdata.type == dns_rdatatype_ns);
dns_rdata_tostruct(&rdata, &ns, NULL);
result = dns_message_findname(
rctx->query->rmessage, DNS_SECTION_ADDITIONAL, &ns.name,
dns_rdatatype_a, 0, NULL, &gluerdataset);
if (result == ISC_R_SUCCESS) {
cache_delegglue(delegset, deleg, &ttl, gluerdataset);
gluerdataset = NULL;
}
result = dns_message_findname(
rctx->query->rmessage, DNS_SECTION_ADDITIONAL, &ns.name,
dns_rdatatype_aaaa, 0, NULL, &gluerdataset);
if (result == ISC_R_SUCCESS) {
cache_delegglue6(delegset, deleg, &ttl, gluerdataset);
gluerdataset = NULL;
}
if (ISC_LIST_EMPTY(deleg->addresses)) {
/*
* There is actually no glues for this NSRRset, so this
* is actually a DNS_DELEGTYPE_NS_NAMES.
*/
deleg->type = DNS_DELEGTYPE_NS_NAMES;
dns_delegset_addns(delegset, deleg, &ns.name);
}
}
result = dns_delegset_insert(delegdb, rctx->ns_name, ttl, delegset);
dns_delegset_detach(&delegset);
return result;
}
static isc_result_t
check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
dns_rdataset_t *found, dns_section_t section) {
@ -6556,13 +6752,9 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
dns_name_t *name = NULL;
bool external;
dns_rdatatype_t rtype;
bool gluing;
REQUIRE(VALID_FCTX(fctx));
gluing = (GLUING(fctx) || (fctx->type == dns_rdatatype_ns &&
dns_name_equal(fctx->name, dns_rootname)));
result = dns_message_findname(rctx->query->rmessage, section, addname,
dns_rdatatype_any, 0, &name, NULL);
if (result == ISC_R_SUCCESS) {
@ -6575,15 +6767,14 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
rtype = rdataset->type;
}
if (dns_rdatatype_isaddr(rtype)) {
mark_related(name, rdataset, external,
gluing);
mark_related(name, rdataset, external);
}
}
} else {
dns_rdataset_t *rdataset = NULL;
result = dns_message_findtype(name, type, 0, &rdataset);
if (result == ISC_R_SUCCESS) {
mark_related(name, rdataset, external, gluing);
mark_related(name, rdataset, external);
if (found != NULL) {
dns_rdataset_clone(rdataset, found);
}
@ -6595,8 +6786,7 @@ check_section(void *arg, const dns_name_t *addname, dns_rdatatype_t type,
name, dns_rdatatype_rrsig, type,
&rdataset);
if (result == ISC_R_SUCCESS) {
mark_related(name, rdataset, external,
gluing);
mark_related(name, rdataset, external);
}
}
}
@ -6835,8 +7025,8 @@ resume_dslookup(void *arg) {
isc_loop_t *loop = resp->loop;
isc_result_t result;
dns_resolver_t *res = NULL;
dns_rdataset_t *frdataset = NULL, *nsrdataset = NULL;
dns_rdataset_t nameservers;
dns_rdataset_t *frdataset = NULL;
dns_delegset_t *delegset = NULL;
dns_fixedname_t fixed;
dns_name_t *domain = NULL;
unsigned int n;
@ -6858,9 +7048,8 @@ resume_dslookup(void *arg) {
}
/* Preserve data from resp before freeing it. */
frdataset = resp->rdataset; /* a.k.a. fctx->nsrrset */
frdataset = resp->rdataset;
result = resp->result;
dns_resolver_freefresp(&resp);
LOCK(&fctx->lock);
@ -6878,15 +7067,21 @@ resume_dslookup(void *arg) {
case ISC_R_SUCCESS:
FCTXTRACE("resuming DS lookup");
dns_rdataset_cleanup(&fctx->nameservers);
dns_rdataset_clone(frdataset, &fctx->nameservers);
/*
* Disassociate now the NS's are saved.
*/
dns_delegset_fromnsrdataset(frdataset, &delegset);
dns_rdataset_cleanup(frdataset);
fctx->ns_ttl = fctx->nameservers.ttl;
if (delegset == NULL) {
result = DNS_R_SERVFAIL;
break;
}
if (fctx->delegset != NULL) {
dns_delegset_detach(&fctx->delegset);
}
dns_delegset_attach(delegset, &fctx->delegset);
fctx->ns_ttl = fctx->delegset->expires - fctx->now;
fctx->ns_ttl_ok = true;
log_ns_ttl(fctx, "resume_dslookup");
@ -6900,8 +7095,6 @@ resume_dslookup(void *arg) {
case ISC_R_SHUTTINGDOWN:
case ISC_R_CANCELED:
/* Don't try anymore. */
/* Can't be done in cleanup. */
dns_rdataset_cleanup(frdataset);
goto cleanup;
@ -6922,11 +7115,9 @@ resume_dslookup(void *arg) {
}
/* Get nameservers from fetch before we destroy it. */
dns_rdataset_init(&nameservers);
if (dns_rdataset_isassociated(&fetch->private->nameservers)) {
dns_rdataset_clone(&fetch->private->nameservers,
&nameservers);
nsrdataset = &nameservers;
if (fetch->private->delegset != NULL) {
dns_delegset_attach(fetch->private->delegset,
&delegset);
/* Get domain from fetch before we destroy it. */
domain = dns_fixedname_initname(&fixed);
@ -6940,7 +7131,7 @@ resume_dslookup(void *arg) {
fetchctx_ref(fctx);
result = dns_resolver_createfetch(
res, fctx->nsname, dns_rdatatype_ns, domain, nsrdataset,
res, fctx->nsname, dns_rdatatype_ns, domain, delegset,
NULL, NULL, 0, fctx->options, 0, fctx->qc, fctx->gqc,
fctx, loop, resume_dslookup, fctx, &fctx->edectx,
&fctx->nsrrset, NULL, &fctx->nsfetch);
@ -6950,11 +7141,12 @@ resume_dslookup(void *arg) {
result = DNS_R_SERVFAIL;
}
}
dns_rdataset_cleanup(&nameservers);
}
cleanup:
if (delegset != NULL) {
dns_delegset_detach(&delegset);
}
dns_resolver_destroyfetch(&fetch);
if (result != ISC_R_SUCCESS) {
@ -8837,9 +9029,6 @@ rctx_authority_negative(respctx_t *rctx) {
rctx->ns_name = name;
rctx->ns_rdataset = rdataset;
}
name->attributes.cache = true;
rdataset->attributes.cache = true;
rdataset->trust = dns_trust_glue;
break;
case dns_rdatatype_soa:
/*
@ -9027,20 +9216,6 @@ rctx_referral(respctx_t *rctx) {
return ISC_R_COMPLETE;
}
/*
* Mark any additional data related to this rdataset.
* It's important that we do this before we change the
* query domain.
*/
INSIST(rctx->ns_rdataset != NULL);
FCTX_ATTR_SET(fctx, FCTX_ATTR_GLUING);
/*
* We want to append **all** the GLUE records here.
*/
(void)dns_rdataset_additionaldata(rctx->ns_rdataset, rctx->ns_name,
check_related, rctx, 0);
FCTX_ATTR_CLR(fctx, FCTX_ATTR_GLUING);
/*
* NS rdatasets with 0 TTL cause problems.
* dns_view_findzonecut() will not find them when we
@ -9052,6 +9227,17 @@ rctx_referral(respctx_t *rctx) {
rctx->ns_rdataset->ttl = 1;
}
/*
* An NS-based delegation can be cached immediately (i.e. there is no
* DNSSEC validation).
*
* For now we don't do anything if the delegation already exists and is
* not expired in the DB. Might be worth a warning? This should never
* happen.
*/
INSIST(rctx->ns_rdataset != NULL);
(void)cache_delegns(rctx);
/*
* Set the current query domain to the referral name.
*
@ -9061,7 +9247,7 @@ rctx_referral(respctx_t *rctx) {
INSIST(dns_name_countlabels(fctx->domain) > 0);
fcount_decr(fctx);
dns_rdataset_cleanup(&fctx->nameservers);
dns_delegset_detach(&fctx->delegset);
dns_name_copy(rctx->ns_name, fctx->domain);
@ -9071,13 +9257,21 @@ rctx_referral(respctx_t *rctx) {
fctx_minimize_qname(fctx);
}
result = fcount_incr(fctx, false);
if (result != ISC_R_SUCCESS) {
rctx->result = result;
return ISC_R_COMPLETE;
if ((fctx->options & DNS_FETCHOPT_QMINFETCH) == 0) {
result = fcount_incr(fctx, false);
if (result != ISC_R_SUCCESS) {
rctx->result = result;
return ISC_R_COMPLETE;
}
}
/*
* While NS and glue records in referral responses are stored in the
* delegation database and not the main cache, other records, such as
* DS, do still need to be stored in the main cache.
*/
FCTX_ATTR_SET(fctx, FCTX_ATTR_WANTCACHE);
fctx->ns_ttl_ok = false;
log_ns_ttl(fctx, "DELEGATION");
rctx->result = DNS_R_DELEGATION;
@ -9185,9 +9379,10 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
} else {
name = fctx->domain;
}
INSIST(fctx->delegset == NULL);
result = dns_view_bestzonecut(fctx->res->view, name, fname,
dcname, fctx->now, findoptions,
true, true, &fctx->nameservers);
true, true, &fctx->delegset);
if (result != ISC_R_SUCCESS) {
FCTXTRACE("couldn't find a zonecut");
fctx_failure_detach(&rctx->fctx, DNS_R_SERVFAIL);
@ -9213,7 +9408,7 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
fctx_failure_detach(&rctx->fctx, DNS_R_SERVFAIL);
return;
}
fctx->ns_ttl = fctx->nameservers.ttl;
fctx->ns_ttl = fctx->delegset->expires - fctx->now;
fctx->ns_ttl_ok = true;
fctx_cancelqueries(fctx, true, false);
fctx_cleanup(fctx);
@ -10070,7 +10265,7 @@ fctx_minimize_qname(fetchctx_t *fctx) {
static isc_result_t
get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
dns_rdataset_t *nameservers, const isc_sockaddr_t *client,
dns_delegset_t *delegset, const isc_sockaddr_t *client,
unsigned int options, unsigned int depth, isc_counter_t *qc,
isc_counter_t *gqc, fetchctx_t *parent, fetchctx_t **fctxp,
bool *new_fctx) {
@ -10093,7 +10288,7 @@ get_attached_fctx(dns_resolver_t *res, isc_loop_t *loop, const dns_name_t *name,
if (fctx == NULL) {
create:
result = fctx_create(res, loop, name, type, domain, nameservers,
result = fctx_create(res, loop, name, type, domain, delegset,
client, options, depth, qc, gqc, parent,
&fctx);
if (result != ISC_R_SUCCESS) {
@ -10194,8 +10389,7 @@ waiting_for_fetch(fetchctx_t *fctx, const dns_name_t *name,
isc_result_t
dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
dns_rdatatype_t type, const dns_name_t *domain,
dns_rdataset_t *nameservers,
dns_forwarders_t *forwarders,
dns_delegset_t *delegset, dns_forwarders_t *forwarders,
const isc_sockaddr_t *client, dns_messageid_t id,
unsigned int options, unsigned int depth,
isc_counter_t *qc, isc_counter_t *gqc,
@ -10218,10 +10412,9 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
REQUIRE(res->frozen);
/* XXXRTH Check for meta type */
if (domain != NULL) {
REQUIRE(DNS_RDATASET_VALID(nameservers));
REQUIRE(nameservers->type == dns_rdatatype_ns);
REQUIRE(DNS_DELEGSET_VALID(delegset));
} else {
REQUIRE(nameservers == NULL);
REQUIRE(delegset == NULL);
}
REQUIRE(forwarders == NULL);
REQUIRE(!dns_rdataset_isassociated(rdataset));
@ -10280,8 +10473,8 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
UNLOCK(&res->lock);
result = get_attached_fctx(res, loop, name, type, domain,
nameservers, client, options, depth,
qc, gqc, parent, &fctx, &new_fctx);
delegset, client, options, depth, qc,
gqc, parent, &fctx, &new_fctx);
if (result != ISC_R_SUCCESS) {
goto fail;
}
@ -10314,7 +10507,7 @@ dns_resolver_createfetch(dns_resolver_t *res, const dns_name_t *name,
}
}
} else {
result = fctx_create(res, loop, name, type, domain, nameservers,
result = fctx_create(res, loop, name, type, domain, delegset,
client, options, depth, qc, gqc, parent,
&fctx);
if (result != ISC_R_SUCCESS) {

View file

@ -173,6 +173,10 @@ destroy(dns_view_t *view) {
isc_refcount_destroy(&view->references);
isc_refcount_destroy(&view->weakrefs);
if (view->deleg != NULL) {
dns_delegdb_detach(&view->deleg);
}
if (view->order != NULL) {
dns_order_detach(&view->order);
}
@ -394,6 +398,10 @@ shutdown_view(dns_view_t *view) {
dns_resolver_shutdown(view->resolver);
}
if (view->deleg != NULL) {
dns_delegdb_shutdown(view->deleg);
}
rcu_read_lock();
adb = rcu_dereference(view->adb);
if (adb != NULL) {
@ -534,6 +542,7 @@ dns_view_createresolver(dns_view_t *view, unsigned int options,
RETERR(dns_resolver_create(view, options, tlsctx_cache, dispatchv4,
dispatchv6, &view->resolver));
isc_mem_create("ADB", &mctx);
dns_adb_create(mctx, view, &view->adb);
isc_mem_detach(&mctx);
@ -979,7 +988,7 @@ dns_view_simplefind(dns_view_t *view, const dns_name_t *name,
}
static isc_result_t
findzonecut_zone(dns_view_t *view, const dns_name_t *name, dns_name_t *fname,
bestzonecut_zone(dns_view_t *view, const dns_name_t *name, dns_name_t *fname,
dns_name_t *dcname, isc_stdtime_t now, unsigned int options,
dns_rdataset_t *rdataset) {
dns_db_t *db = NULL;
@ -1052,15 +1061,14 @@ cleanup:
}
static isc_result_t
findzonecut_cache(dns_view_t *view, const dns_name_t *name, dns_name_t *fname,
dns_name_t *dcname, isc_stdtime_t now, unsigned int options,
dns_rdataset_t *rdataset) {
bestzonecut_delegdb(dns_view_t *view, const dns_name_t *name, dns_name_t *fname,
dns_name_t *dcname, isc_stdtime_t now, unsigned int options,
dns_delegset_t **delegsetp) {
isc_result_t result = DNS_R_NXDOMAIN;
if (view->cachedb != NULL) {
result = dns_db_findzonecut(view->cachedb, name, options, now,
NULL, fname, dcname, rdataset,
NULL);
if (view->deleg != NULL) {
result = dns_delegdb_lookup(view->deleg, name, now, options,
fname, dcname, delegsetp);
}
/*
@ -1069,26 +1077,26 @@ findzonecut_cache(dns_view_t *view, const dns_name_t *name, dns_name_t *fname,
* keep DNS_R_NXDOMAIN, so the hints can be checked.
*/
if (result != ISC_R_SUCCESS) {
dns_rdataset_cleanup(rdataset);
result = DNS_R_NXDOMAIN;
}
return result;
}
static void
findzonecut_zoneorcache(dns_view_t *view, const dns_name_t *name,
bestzonecut_zoneorcache(dns_view_t *view, const dns_name_t *name,
dns_name_t *fname, dns_name_t *dcname,
isc_stdtime_t now, unsigned int options,
dns_rdataset_t *rdataset) {
dns_rdataset_t *rdataset, dns_delegset_t **delegsetp) {
isc_result_t result;
dns_rdataset_t crdataset = DNS_RDATASET_INIT;
dns_fixedname_t f, dc;
dns_name_t *cfname = dns_fixedname_initname(&f);
dns_name_t *cdcname = dns_fixedname_initname(&dc);
CHECK(findzonecut_cache(view, name, cfname, cdcname, now, options,
&crdataset));
result = bestzonecut_delegdb(view, name, cfname, cdcname, now, options,
delegsetp);
if (result != ISC_R_SUCCESS) {
return;
}
bool cacheclosest = dns_name_issubdomain(cfname, fname);
bool staticstub = rdataset->attributes.staticstub &&
@ -1096,20 +1104,18 @@ findzonecut_zoneorcache(dns_view_t *view, const dns_name_t *name,
if (cacheclosest && !staticstub) {
dns_rdataset_cleanup(rdataset);
dns_rdataset_clone(&crdataset, rdataset);
dns_name_copy(cfname, fname);
if (dcname != NULL) {
dns_name_copy(cdcname, dcname);
}
} else {
dns_delegset_detach(delegsetp);
}
cleanup:
dns_rdataset_cleanup(&crdataset);
}
static isc_result_t
findzonecut_hints(dns_view_t *view, dns_name_t *fname, dns_name_t *dcname,
bestzonecut_hints(dns_view_t *view, dns_name_t *fname, dns_name_t *dcname,
isc_stdtime_t now, dns_rdataset_t *rdataset) {
isc_result_t result = ISC_R_NOTFOUND;
@ -1132,13 +1138,20 @@ isc_result_t
dns_view_bestzonecut(dns_view_t *view, const dns_name_t *name,
dns_name_t *fname, dns_name_t *dcname, isc_stdtime_t now,
unsigned int options, bool usehints, bool usecache,
dns_rdataset_t *rdataset) {
dns_delegset_t **delegsetp) {
isc_result_t result;
dns_rdataset_t rdatasetdata = DNS_RDATASET_INIT;
dns_rdataset_t *rdataset = NULL;
REQUIRE(DNS_VIEW_VALID(view));
REQUIRE(view->frozen);
result = findzonecut_zone(view, name, fname, dcname, now, options,
if (delegsetp != NULL) {
REQUIRE(*delegsetp == NULL);
rdataset = &rdatasetdata;
}
result = bestzonecut_zone(view, name, fname, dcname, now, options,
rdataset);
if (result == DNS_R_NXDOMAIN && usecache) {
@ -1146,29 +1159,41 @@ dns_view_bestzonecut(dns_view_t *view, const dns_name_t *name,
* No local zone matches `name`, but the cache might have a
* delegation.
*/
result = findzonecut_cache(view, name, fname, dcname, now,
options, rdataset);
result = bestzonecut_delegdb(view, name, fname, dcname, now,
options, delegsetp);
} else if (result == ISC_R_SUCCESS && usecache) {
/*
* A zone with a (possibly partial) delegation match but the
* cache can have a more precise delegation.
*/
findzonecut_zoneorcache(view, name, fname, dcname, now, options,
rdataset);
bestzonecut_zoneorcache(view, name, fname, dcname, now, options,
rdataset, delegsetp);
}
/*
* No local zone nor cache match. Last attempt with the hints.
*/
if (result == DNS_R_NXDOMAIN && usehints) {
result = findzonecut_hints(view, fname, dcname, now, rdataset);
result = bestzonecut_hints(view, fname, dcname, now, rdataset);
}
if (result != ISC_R_SUCCESS) {
result = DNS_R_NXDOMAIN;
dns_rdataset_cleanup(rdataset);
} else {
/*
* The rdataset came either from a local zone or a hint. Either
* way, we only considering the NS rdataset here, so if there
* are glues, they'll be ignored. This is okay: the delegation
* type will be DNS_DELEGSET_NS_NAMES, so ADB will do a NS name
* lookup but immediately find the results locally (because this
* came from a local zone or hint). So the resolution will be
* the same, and this avoid adding extra code here to extract
* A/AAAA rdataset if any.
*/
dns_delegset_fromnsrdataset(rdataset, delegsetp);
}
dns_rdataset_cleanup(rdataset);
return result;
}

View file

@ -20,6 +20,7 @@
#include <sys/types.h>
#include <sys/un.h>
#include <isc/list.h>
#include <isc/net.h>
#include <isc/types.h>
@ -36,6 +37,11 @@ struct isc_netaddr {
uint32_t zone;
};
struct isc_netaddrlink {
isc_netaddr_t addr;
ISC_LINK(isc_netaddrlink_t) link;
};
struct isc_netprefix {
isc_netaddr_t addr;
unsigned int prefixlen;

View file

@ -53,16 +53,18 @@ typedef struct isc_loop isc_loop_t; /*%< Event loop */
typedef struct isc_mem isc_mem_t; /*%< Memory */
typedef struct isc_mempool isc_mempool_t; /*%< Memory Pool */
typedef struct isc_netaddr isc_netaddr_t; /*%< Net Address */
typedef struct isc_netprefix isc_netprefix_t; /*%< Net Prefix */
typedef struct isc_nmsocket isc_nmsocket_t; /*%< Network manager socket */
typedef struct isc_nmhandle isc_nmhandle_t; /*%< Network manager handle */
typedef struct isc_portset isc_portset_t; /*%< Port Set */
typedef struct isc_quota isc_quota_t; /*%< Quota */
typedef struct isc_ratelimiter isc_ratelimiter_t; /*%< Rate Limiter */
typedef struct isc_region isc_region_t; /*%< Region */
typedef struct isc_rlevent isc_rlevent_t; /*%< Rate Limiter Event */
typedef struct isc_signal isc_signal_t; /*%< Signal handler */
typedef struct isc_sockaddr isc_sockaddr_t; /*%< Socket Address */
typedef struct isc_netaddrlink isc_netaddrlink_t; /*%< Linkable Net Address */
typedef ISC_LIST(isc_netaddrlink_t) isc_netaddrlist_t; /*%< Net Address List */
typedef struct isc_netprefix isc_netprefix_t; /*%< Net Prefix */
typedef struct isc_nmsocket isc_nmsocket_t; /*%< Network manager socket */
typedef struct isc_nmhandle isc_nmhandle_t; /*%< Network manager handle */
typedef struct isc_portset isc_portset_t; /*%< Port Set */
typedef struct isc_quota isc_quota_t; /*%< Quota */
typedef struct isc_ratelimiter isc_ratelimiter_t; /*%< Rate Limiter */
typedef struct isc_region isc_region_t; /*%< Region */
typedef struct isc_rlevent isc_rlevent_t; /*%< Rate Limiter Event */
typedef struct isc_signal isc_signal_t; /*%< Signal handler */
typedef struct isc_sockaddr isc_sockaddr_t; /*%< Socket Address */
typedef ISC_LIST(isc_sockaddr_t) isc_sockaddrlist_t; /*%< Socket Address List
* */
typedef struct isc_stats isc_stats_t; /*%< Statistics */

View file

@ -260,8 +260,7 @@ ns_query_done(query_ctx_t *qctx);
isc_result_t
ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
dns_name_t *qdomain, dns_rdataset_t *nameservers,
bool resuming);
dns_name_t *qdomain, dns_delegset_t *delegset, bool resuming);
/*%<
* Prepare client for recursion, then create a resolver fetch, with
* the event callback set to fetch_callback(). Afterward we terminate

View file

@ -3146,6 +3146,8 @@ rpz_rrset_find(ns_client_t *client, dns_name_t *name, dns_rdatatype_t type,
if (result == ISC_R_NOTFOUND) {
result = DNS_R_DELEGATION;
}
} else if (result == ISC_R_NOTFOUND && !is_zone) {
result = DNS_R_DELEGATION;
}
rpz_clean(NULL, dbp, &node, NULL);
if (result == DNS_R_DELEGATION) {
@ -6226,8 +6228,7 @@ release_recursionquota(ns_client_t *client) {
isc_result_t
ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
dns_name_t *qdomain, dns_rdataset_t *nameservers,
bool resuming) {
dns_name_t *qdomain, dns_delegset_t *delegset, bool resuming) {
isc_result_t result;
dns_rdataset_t *rdataset, *sigrdataset;
isc_sockaddr_t *peeraddr = NULL;
@ -6255,7 +6256,6 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
/*
* Invoke the resolver.
*/
REQUIRE(nameservers == NULL || nameservers->type == dns_rdatatype_ns);
REQUIRE(FETCH_RECTYPE_NORMAL(client) == NULL);
rdataset = ns_client_newrdataset(client);
@ -6278,11 +6278,11 @@ ns_query_recurse(ns_client_t *client, dns_rdatatype_t qtype, dns_name_t *qname,
&HANDLE_RECTYPE_NORMAL(client));
maybe_init_fetch_counter(client);
result = dns_resolver_createfetch(
client->inner.view->resolver, qname, qtype, qdomain,
nameservers, NULL, peeraddr, client->message->id,
client->query.fetchoptions, 0, NULL, client->query.qc, NULL,
client->manager->loop, fetch_callback, client, &client->edectx,
rdataset, sigrdataset, &FETCH_RECTYPE_NORMAL(client));
client->inner.view->resolver, qname, qtype, qdomain, delegset,
NULL, peeraddr, client->message->id, client->query.fetchoptions,
0, NULL, client->query.qc, NULL, client->manager->loop,
fetch_callback, client, &client->edectx, rdataset, sigrdataset,
&FETCH_RECTYPE_NORMAL(client));
if (result != ISC_R_SUCCESS) {
release_recursionquota(client);
@ -6817,7 +6817,7 @@ query_checkrrl(query_ctx_t *qctx, isc_result_t result) {
if (qctx->view->rrl != NULL && !HAVECOOKIE(qctx->client) &&
((qctx->fname != NULL && dns_name_isabsolute(qctx->fname)) ||
(result == ISC_R_NOTFOUND && !RECURSIONOK(qctx->client))) &&
!(result == DNS_R_DELEGATION && !qctx->is_zone &&
!(result == ISC_R_NOTFOUND && !qctx->is_zone &&
RECURSIONOK(qctx->client)) &&
(qctx->client->query.rpz_st == NULL ||
(qctx->client->query.rpz_st->state & DNS_RPZ_REWRITTEN) == 0) &&
@ -8659,9 +8659,30 @@ query_delegation_recurse(query_ctx_t *qctx) {
/*
* Any other recursion.
*/
result = ns_query_recurse(qctx->client, qctx->qtype, qname,
qctx->fname, qctx->rdataset,
qctx->resuming);
dns_delegset_t *delegset = NULL;
dns_fixedname_t ffname;
dns_name_t *fname = dns_fixedname_initname(&ffname);
isc_result_t tresult;
tresult = dns_view_bestzonecut(qctx->view, qname, fname, NULL,
qctx->client->inner.now, 0, true,
true, &delegset);
if (tresult != ISC_R_SUCCESS) {
dns_delegset_fromnsrdataset(qctx->rdataset, &delegset);
fname = qctx->fname;
}
if (delegset == NULL) {
result = ISC_R_NOTFOUND;
} else {
result = ns_query_recurse(qctx->client, qctx->qtype,
qname, fname, delegset,
qctx->resuming);
}
if (delegset != NULL) {
dns_delegset_detach(&delegset);
}
}
if (result == ISC_R_SUCCESS) {
@ -10574,7 +10595,7 @@ query_addbestns(query_ctx_t *qctx) {
dns_name_t *fname = NULL, *zfname = NULL;
dns_rdataset_t *rdataset = NULL, *sigrdataset = NULL;
dns_rdataset_t *zrdataset = NULL, *zsigrdataset = NULL;
bool is_zone = false, use_zone = false;
bool is_zone = false;
isc_buffer_t *dbuf = NULL;
isc_result_t result;
dns_dbversion_t *version = NULL;
@ -10665,33 +10686,7 @@ db_find:
is_zone = false;
goto db_find;
}
} else {
result = dns_db_findzonecut(db, client->query.qname,
client->query.dboptions,
client->inner.now, &node, fname,
NULL, rdataset, sigrdataset);
if (result == ISC_R_SUCCESS) {
if (zfname != NULL &&
!dns_name_issubdomain(fname, zfname))
{
/*
* We found a zonecut in the cache, but our
* zone delegation is better.
*/
use_zone = true;
}
} else if (result == ISC_R_NOTFOUND && zfname != NULL) {
/*
* We didn't find anything in the cache, but we
* have a zone delegation, so use it.
*/
use_zone = true;
} else {
goto cleanup;
}
}
if (use_zone) {
} else if (zfname != NULL) {
ns_client_releasename(client, &fname);
/*
* We've already done ns_client_keepname() on
@ -10714,6 +10709,8 @@ db_find:
fname = MOVE_OWNERSHIP(zfname);
rdataset = MOVE_OWNERSHIP(zrdataset);
sigrdataset = MOVE_OWNERSHIP(zsigrdataset);
} else {
goto cleanup;
}
/*

723
tests/dns/deleg_test.c Normal file
View file

@ -0,0 +1,723 @@
/*
* 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.
*/
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
/*
* Mock isc_stdtime_now() as it makes testing easier (to compare
* generated/expected deleg data).
*/
static uint32_t stdtime_now = 100;
static uint32_t
isc_stdtime_now(void) {
return stdtime_now;
}
#include <isc/lib.h>
#include <isc/list.h>
#include <isc/netaddr.h>
#include <isc/stdtime.h>
#include <dns/deleg.h>
#include <dns/fixedname.h>
#include <dns/lib.h>
#include <dns/name.h>
/*
* Because of the mock above.
*/
#include "../dns/deleg.c"
#include <tests/isc.h>
static void
shutdownloop(ISC_ATTR_UNUSED void *arg) {
isc_loopmgr_shutdown();
}
static void
shutdowntest(dns_delegdb_t **dbp) {
dns_delegdb_shutdown(*dbp);
dns_delegdb_detach(dbp);
shutdownloop(NULL);
}
static void
rundelegtest(isc_job_cb testcb) {
isc_loopmgr_create(isc_g_mctx, 1);
isc_loop_setup(isc_loop_main(), testcb, NULL);
isc_loopmgr_run();
isc_loopmgr_destroy();
}
static void
addnamedeleg(const char *addrstr, dns_delegset_t *delegset, dns_deleg_t *deleg,
void (*fn)(dns_delegset_t *, dns_deleg_t *, const dns_name_t *)) {
dns_fixedname_t fname;
dns_name_t *name = dns_fixedname_initname(&fname);
dns_name_fromstring(name, addrstr, NULL, 0, NULL);
fn(delegset, deleg, name);
}
static void
addipdeleg(unsigned int af, const char *addrstr, dns_delegset_t *delegset,
dns_deleg_t *deleg) {
isc_netaddr_t addr = { .family = af };
assert_true(af == AF_INET || af == AF_INET6);
assert_int_equal(inet_pton(af, addrstr, &addr.type), 1);
dns_delegset_addaddr(delegset, deleg, &addr);
}
static void
writedb(dns_delegdb_t *db, const char *zonecutstr, dns_ttl_t expire,
dns_delegset_t **delegsetp, bool expectsuccess) {
dns_fixedname_t fzonecut;
dns_name_t *zonecut = dns_fixedname_initname(&fzonecut);
isc_result_t result;
dns_name_fromstring(zonecut, zonecutstr, NULL, 0, NULL);
result = dns_delegset_insert(db, zonecut, expire, *delegsetp);
dns_delegset_detach(delegsetp);
assert_null(*delegsetp);
if (expectsuccess) {
assert_int_equal(result, ISC_R_SUCCESS);
} else {
assert_int_equal(result, ISC_R_EXISTS);
}
}
static isc_result_t
lookupdb(dns_delegdb_t *db, const char *namestr, isc_stdtime_t now,
unsigned int options, const char *expectedzcstr,
dns_delegset_t **delegsetp) {
isc_result_t result;
dns_fixedname_t fname, fexpectedzc, fzonecut;
dns_name_t *name = dns_fixedname_initname(&fname),
*expectedzc = dns_fixedname_initname(&fexpectedzc),
*zonecut = dns_fixedname_initname(&fzonecut);
dns_name_fromstring(expectedzc, expectedzcstr, NULL, 0, NULL);
dns_name_fromstring(name, namestr, NULL, 0, NULL);
result = dns_delegdb_lookup(db, name, now, options, zonecut, NULL,
delegsetp);
if (result == ISC_R_SUCCESS) {
assert_non_null(*delegsetp);
assert_true(dns_name_equal(zonecut, expectedzc));
} else {
assert_null(*delegsetp);
}
return result;
}
static void
dumpdb(dns_delegdb_t *db, bool expired, const char *expected) {
constexpr char *filename = "delegdb-dump-test.db";
char buffer[1024 * 4] = { 0 };
FILE *fp = fopen(filename, "w+");
REQUIRE(fp != NULL);
dns_delegdb_dump(db, expired, fp);
fp = freopen(filename, "r", fp);
REQUIRE(fp != NULL);
REQUIRE(fread(buffer, sizeof(buffer) - 1, 1, fp) == 0);
assert_string_equal(expected, buffer);
REQUIRE(fclose(fp) == 0);
REQUIRE(unlink(filename) == 0);
}
static void
basictests(ISC_ATTR_UNUSED void *arg) {
isc_result_t result;
dns_delegdb_t *db = NULL;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
isc_stdtime_t now = isc_stdtime_now();
dns_delegdb_create(&db);
assert_non_null(db);
/*
* A non expired delegation for foo. zonecut
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
addnamedeleg("ns.foo.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
addipdeleg(AF_INET, "1.2.3.4", delegset, deleg);
addipdeleg(AF_INET6, "1111:2222:3333::4444", delegset, deleg);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_NAMES, &deleg);
assert_non_null(deleg);
addnamedeleg("ns.example.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
writedb(db, "foo.", 30, &delegset, true);
result = lookupdb(db, "baz.bar.gee.", 0, 0, "", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
result = lookupdb(db, "baz.bar.foo.", 0, 0, "foo.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
/*
* A non expired delegation for bar.foo. zonecut
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_NAMES, &deleg);
addnamedeleg("ns.bar.foo.", delegset, deleg, dns_delegset_addns);
addnamedeleg("ns2.bar.foo.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
addipdeleg(AF_INET, "8.9.10.11", delegset, deleg);
addipdeleg(AF_INET, "9.9.10.12", delegset, deleg);
addipdeleg(AF_INET6, "ACDC::ACDC", delegset, deleg);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "ABBA::ABBA", delegset, deleg);
addipdeleg(AF_INET, "13.14.15.16", delegset, deleg);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_PARAMS, &deleg);
addnamedeleg("delegns.gee.", delegset, deleg,
dns_delegset_adddelegparam);
addnamedeleg("delegns2.gee.", delegset, deleg,
dns_delegset_adddelegparam);
deleg = NULL;
writedb(db, "bar.foo.", 25, &delegset, true);
result = lookupdb(db, "baz.bar.gee.", 0, 0, "", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
result = lookupdb(db, "baz.bar.foo.", 0, 0, "bar.foo.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
/*
* A expired delegation for bar.stuff. zonecut
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
deleg = NULL;
writedb(db, "bar.stuff.", 10, &delegset, true);
deleg = NULL;
result = lookupdb(db, "baz.bar.stuff.", now + 10, 0, "", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
/*
* But, if we ask for a date before its expiration, it is visible. And
* it is possible to dump it as well. But of course the dump when
* expired won't get anythig.
*/
result = lookupdb(db, "baz.bar.stuff.", now + 9, 0, "bar.stuff.",
&delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
/*
* A non expired delegation for bar.stuff. zonecut replace the expired
* one. Move the time forward 10 to make the entry expired.
*/
stdtime_now += 10;
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES, &deleg);
addipdeleg(AF_INET6, "1111::3333", delegset, deleg);
deleg = NULL;
writedb(db, "bar.stuff.", 2, &delegset, true);
/*
* Attempt to override bar.stuff. even though the existing delegation is
* not expired. This will be rejected.
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
addnamedeleg("wontbeindb.bar.stuff.", delegset, deleg,
dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "acdc::acdc", delegset, deleg);
deleg = NULL;
writedb(db, "bar.stuff.", 2, &delegset, false);
deleg = NULL;
result = lookupdb(db, "stuff.", 0, 0, "", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
result = lookupdb(db, "idonotknowthis.at.all.stuff.", 0, 0, "",
&delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
result = lookupdb(db, "baz.bar.stuff.", 0, 0, "bar.stuff.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
char expected_dbdump[] =
"foo. 20 DELEG server-name=ns.foo.\n"
"foo. 20 DELEG server-ipv4=1.2.3.4 "
"server-ipv6=1111:2222:3333::4444\n"
"foo. 20 DELEG server-name=ns.example.\n"
"bar.foo. 15 DELEG server-name=ns.bar.foo.,ns2.bar.foo.\n"
"bar.foo. 15 DELEG server-ipv4=8.9.10.11,9.9.10.12 "
"server-ipv6=acdc::acdc\n"
"bar.foo. 15 DELEG server-ipv4=13.14.15.16 "
"server-ipv6=abba::abba\n"
"bar.foo. 15 DELEG "
"include-delegparam=delegns.gee.,delegns2.gee.\n"
"bar.stuff. 2 DELEG server-name=ns.bar.stuff.\n"
"bar.stuff. 2 DELEG server-ipv6=1111::3333\n";
dumpdb(db, false, expected_dbdump);
/*
* Dump in the "future", everything is seen as expired
*/
stdtime_now += 300;
dumpdb(db, false, "");
/*
* Bump if we ask to dump expired entries, they'll be there (with TTL 0)
*/
char expected_expired_dbdump[] =
"foo. 0 DELEG server-name=ns.foo.\n"
"foo. 0 DELEG server-ipv4=1.2.3.4 "
"server-ipv6=1111:2222:3333::4444\n"
"foo. 0 DELEG server-name=ns.example.\n"
"bar.foo. 0 DELEG server-name=ns.bar.foo.,ns2.bar.foo.\n"
"bar.foo. 0 DELEG server-ipv4=8.9.10.11,9.9.10.12 "
"server-ipv6=acdc::acdc\n"
"bar.foo. 0 DELEG server-ipv4=13.14.15.16 "
"server-ipv6=abba::abba\n"
"bar.foo. 0 DELEG "
"include-delegparam=delegns.gee.,delegns2.gee.\n"
"bar.stuff. 0 DELEG server-name=ns.bar.stuff.\n"
"bar.stuff. 0 DELEG server-ipv6=1111::3333\n";
dumpdb(db, true, expected_expired_dbdump);
shutdowntest(&db);
}
static void
ttl0tests(ISC_ATTR_UNUSED void *arg) {
isc_result_t result;
dns_delegdb_t *db = NULL;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
isc_stdtime_t now = isc_stdtime_now();
isc_buffer_t b;
char bdata[2048];
isc_buffer_init(&b, bdata, sizeof(bdata));
dns_delegdb_create(&db);
assert_non_null(db);
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_NAMES, &deleg);
addnamedeleg("ns.bar.stuff.", delegset, deleg, dns_delegset_addns);
deleg = NULL;
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
deleg = NULL;
writedb(db, "bar.stuff.", 0, &delegset, true);
deleg = NULL;
result = lookupdb(db, "baz.bar.stuff.", now, 0, "bar.stuff.",
&delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
result = lookupdb(db, "baz.bar.stuff.", now + 1, 0, "", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
shutdowntest(&db);
}
static void
noexacttests(ISC_ATTR_UNUSED void *arg) {
isc_result_t result;
dns_delegdb_t *db = NULL;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
isc_stdtime_t now = isc_stdtime_now();
isc_buffer_t b;
char bdata[2048];
isc_buffer_init(&b, bdata, sizeof(bdata));
dns_delegdb_create(&db);
assert_non_null(db);
struct {
const char *name;
const char *expected;
const char *noexactexpected;
dns_ttl_t ttl;
} zonecuts[] = {
{ "stuff.", "stuff.", "stuff.", 30 },
{ "foo.stuff.", "foo.stuff.", "stuff.", 30 },
{ "expired.foo.stuff.", "foo.stuff.", "foo.stuff.", 1 },
{ "bar.expired.foo.stuff.", "bar.expired.foo.stuff.",
"foo.stuff.", 30 },
{ "baz.bar.expired.foo.stuff.", "baz.bar.expired.foo.stuff.",
"bar.expired.foo.stuff.", 30 }
};
for (size_t i = 0; i < ARRAY_SIZE(zonecuts); i++) {
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_NS_GLUES,
&deleg);
addipdeleg(AF_INET6, "1111::1111", delegset, deleg);
writedb(db, zonecuts[i].name, zonecuts[i].ttl, &delegset, true);
deleg = NULL;
}
for (size_t i = 0; i < ARRAY_SIZE(zonecuts); i++) {
result = lookupdb(db, zonecuts[i].name, now + 1, 0,
zonecuts[i].expected, &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
result = lookupdb(db, zonecuts[i].name, now + 1,
DNS_DBFIND_NOEXACT,
zonecuts[i].noexactexpected, &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
}
result = lookupdb(db, "gee.expired.foo.stuff.", now + 1, 0,
"foo.stuff.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
shutdowntest(&db);
}
static void
deletetests(ISC_ATTR_UNUSED void *arg) {
isc_result_t result;
dns_delegdb_t *db = NULL;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
dns_fixedname_t fname;
dns_name_t *name = dns_fixedname_initname(&fname);
dns_delegdb_create(&db);
assert_non_null(db);
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "stuff.", 10, &delegset, true);
deleg = NULL;
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "baz.stuff.", 10, &delegset, true);
deleg = NULL;
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "bar.baz.stuff.", 10, &delegset, true);
deleg = NULL;
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "foo.bar.baz.stuff.", 10, &delegset, true);
deleg = NULL;
dns_name_fromstring(name, "foo.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_NOTFOUND);
result = dns_delegdb_delete(db, name, true);
assert_int_equal(result, ISC_R_NOTFOUND);
dns_name_fromstring(name, "gee.foo.bar.stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_NOTFOUND);
dns_name_fromstring(name, "foo.bar.baz.stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_SUCCESS);
dns_name_fromstring(name, "foo.bar.baz.stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_NOTFOUND);
dns_name_fromstring(name, "baz.stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_SUCCESS);
result = lookupdb(db, "bar.baz.stuff.", 5, 0, "bar.baz.stuff.",
&delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
dns_name_fromstring(name, "stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, true);
assert_int_equal(result, ISC_R_SUCCESS);
dns_name_fromstring(name, "stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_NOTFOUND);
dns_name_fromstring(name, "bar.baz.stuff.", NULL, 0, NULL);
result = dns_delegdb_delete(db, name, false);
assert_int_equal(result, ISC_R_NOTFOUND);
result = lookupdb(db, "bar.baz.stuff.", 5, 0, "bar.baz.stuff.",
&delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
/*
* Let's add stuff. back and query bar.baz.stuff. again. Because the
* node is NULL, it should go up until it finds stuff.
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "stuff.", 10, &delegset, true);
deleg = NULL;
result = lookupdb(db, "bar.baz.stuff.", 5, 0, "stuff.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
shutdowntest(&db);
}
/*
* The cleanup test is split into phases because node destruction is now
* fully deferred to the node's owning loop via isc_async_run(). After
* rcu_barrier() completes, the QP reclamation has fired (calling
* delegdb_node_destroy which schedules the async callback), but the
* actual memory free hasn't happened yet it's pending on the loop's
* event queue. We must return to the loop between phases so it can
* process the pending node destroys before we check memory usage.
*/
typedef struct {
dns_delegdb_t *db;
isc_stdtime_t now;
} cleanup_ctx_t;
static void
cleanuptests_phase3(void *arg) {
cleanup_ctx_t *ctx = arg;
dns_delegdb_t *db = ctx->db;
isc_stdtime_t now = ctx->now;
dns_delegset_t *delegset = NULL;
isc_result_t result;
assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
/*
* baz. is there, but bar. is gone, as it has been
* removed (even if it wasn't expired.)
*/
result = lookupdb(db, "baz.", now, 0, "baz.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
result = lookupdb(db, "bar.", now, 0, "bar.", &delegset);
assert_int_equal(result, ISC_R_NOTFOUND);
shutdowntest(&db);
}
static void
cleanuptests_phase2(void *arg) {
cleanup_ctx_t *ctx = arg;
dns_delegdb_t *db = ctx->db;
isc_stdtime_t now = ctx->now;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
isc_result_t result;
assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
/*
* bar. is there
*/
result = lookupdb(db, "bar.", now, 0, "bar.", &delegset);
assert_int_equal(result, ISC_R_SUCCESS);
dns_delegset_detach(&delegset);
/*
* Add yet another non expired record. But LRU will have to get
* rid of it because we're hitting the hiwater mark again.
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
for (size_t i = 0; i < 99999; i++) {
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
}
assert_int_in_range(isc_mem_inuse(db->mctx), 8000000, 8100000);
writedb(db, "baz.", 30, &delegset, true);
deleg = NULL;
rcu_barrier();
isc_async_run(isc_loop(), cleanuptests_phase3, ctx);
}
static void
cleanuptests(ISC_ATTR_UNUSED void *arg) {
static cleanup_ctx_t ctx;
dns_delegdb_t *db = NULL;
dns_deleg_t *deleg = NULL;
dns_delegset_t *delegset = NULL;
dns_delegdb_create(&db);
assert_non_null(db);
ctx = (cleanup_ctx_t){ .db = db, .now = isc_stdtime_now() };
/*
* hiwater is 4375000 = 5000000 - (5000000 >> 3)
* lowater is 3750000 = 5000000 - (5000000 >> 2)
*/
dns_delegdb_setsize(db, 5000000);
/*
* A valid record
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
writedb(db, "baz.", 300, &delegset, true);
deleg = NULL;
/*
* An expired record
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
assert_int_in_range(isc_mem_inuse(db->mctx), 500, 2000);
for (size_t i = 0; i < 99999; i++) {
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
}
assert_int_in_range(isc_mem_inuse(db->mctx), 4000000, 4100000);
writedb(db, "stuff.", 10, &delegset, true);
deleg = NULL;
stdtime_now += 10;
/*
* A non expired record
*/
dns_delegset_allocset(db, &delegset);
dns_delegset_allocdeleg(delegset, DNS_DELEGTYPE_DELEG_ADDRESSES,
&deleg);
for (size_t i = 0; i < 99999; i++) {
addipdeleg(AF_INET6, "1111::2222", delegset, deleg);
}
/*
* The zonecut is not added yet but the delegset being huge (allocated
* with DB mem context) overmem conditions will be detected, and the
* expired node will be removed
*/
assert_int_in_range(isc_mem_inuse(db->mctx), 8000000, 8100000);
writedb(db, "bar.", 30, &delegset, true);
deleg = NULL;
/*
* stuff. internal node (and delegset) is now removed. rcu_barrier()
* is needed to kick off QP reclamation flow (and run the detaching
* functions from the DB nodes). The actual memory free is deferred
* to the loop via isc_async_run(), so we continue in phase2 to let
* the loop process the pending node destroys.
*/
rcu_barrier();
isc_async_run(isc_loop(), cleanuptests_phase2, &ctx);
}
ISC_RUN_TEST_IMPL(dns_deleg_basictests) { rundelegtest(basictests); }
ISC_RUN_TEST_IMPL(dns_deleg_ttl0tests) { rundelegtest(ttl0tests); }
ISC_RUN_TEST_IMPL(dns_deleg_noexacttests) { rundelegtest(noexacttests); }
ISC_RUN_TEST_IMPL(dns_deleg_deletetests) { rundelegtest(deletetests); }
ISC_RUN_TEST_IMPL(dns_deleg_cleanuptests) { rundelegtest(cleanuptests); }
ISC_TEST_LIST_START
ISC_TEST_ENTRY(dns_deleg_basictests)
ISC_TEST_ENTRY(dns_deleg_ttl0tests)
ISC_TEST_ENTRY(dns_deleg_noexacttests)
ISC_TEST_ENTRY(dns_deleg_deletetests)
ISC_TEST_ENTRY(dns_deleg_cleanuptests)
ISC_TEST_LIST_END
ISC_TEST_MAIN

View file

@ -17,6 +17,7 @@ dns_tests = [
'dbdiff',
'dbiterator',
'dbversion',
'deleg',
'dispatch',
'dns64',
'dst',