mirror of
https://github.com/isc-projects/bind9.git
synced 2026-05-25 19:02:12 -04:00
Merge tag 'v9.18.49' into bind-9.18
This commit is contained in:
commit
ec35c78729
78 changed files with 3179 additions and 466 deletions
|
|
@ -114,6 +114,8 @@ extern unsigned int dns_zone_mkey_hour;
|
|||
extern unsigned int dns_zone_mkey_day;
|
||||
extern unsigned int dns_zone_mkey_month;
|
||||
|
||||
extern size_t dns_adb_addrslimit;
|
||||
|
||||
static bool want_stats = false;
|
||||
static char program_name[NAME_MAX] = "named";
|
||||
static char absolute_conffile[PATH_MAX];
|
||||
|
|
@ -805,6 +807,13 @@ parse_T_opt(char *option) {
|
|||
transferstuck = true;
|
||||
} else if (!strncmp(option, "tat=", 4)) {
|
||||
named_g_tat_interval = atoi(option + 4);
|
||||
} else if (!strncmp(option, "adbaddrslimit=", 14)) {
|
||||
size_t adb_addrslimit = atoi(option + 14);
|
||||
if (adb_addrslimit < 1) {
|
||||
named_main_earlyfatal("adbaddrslimit must be at "
|
||||
"least 1");
|
||||
}
|
||||
dns_adb_addrslimit = adb_addrslimit;
|
||||
} else {
|
||||
fprintf(stderr, "unknown -T flag '%s'\n", option);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1987,10 +1987,12 @@ dlzconfigure_callback(dns_view_t *view, dns_dlzdb_t *dlzdb, dns_zone_t *zone) {
|
|||
dns_rdataclass_t zclass = view->rdclass;
|
||||
isc_result_t result;
|
||||
|
||||
dns_zone_setclass(zone, zclass);
|
||||
result = dns_zonemgr_managezone(named_g_server->zonemgr, zone);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
return result;
|
||||
}
|
||||
|
||||
dns_zone_setstats(zone, named_g_server->zonestats);
|
||||
|
||||
return named_zone_configure_writeable_dlz(dlzdb, zone, zclass, origin);
|
||||
|
|
@ -4515,6 +4517,7 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
|||
obj = NULL;
|
||||
result = named_config_get(maps, "max-cache-size", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
|
||||
/*
|
||||
* If "-T maxcachesize=..." is in effect, it overrides any other
|
||||
* "max-cache-size" setting found in configuration, either implicit or
|
||||
|
|
@ -5224,34 +5227,15 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
|||
}
|
||||
|
||||
/*
|
||||
* We have default hints for class IN if we need them.
|
||||
* We have default root hints for class IN if we need them.
|
||||
* Each view gets its own rootdb so a priming response only
|
||||
* writes into that view's copy. Other classes don't support
|
||||
* recursion and don't need hints.
|
||||
*/
|
||||
if (view->rdclass == dns_rdataclass_in && view->hints == NULL) {
|
||||
dns_view_sethints(view, named_g_server->in_roothints);
|
||||
}
|
||||
|
||||
/*
|
||||
* If we still have no hints, this is a non-IN view with no
|
||||
* "hints zone" configured. Issue a warning, except if this
|
||||
* is a root server. Root servers never need to consult
|
||||
* their hints, so it's no point requiring users to configure
|
||||
* them.
|
||||
*/
|
||||
if (view->hints == NULL) {
|
||||
dns_zone_t *rootzone = NULL;
|
||||
(void)dns_view_findzone(view, dns_rootname, &rootzone);
|
||||
if (rootzone != NULL) {
|
||||
dns_zone_detach(&rootzone);
|
||||
need_hints = false;
|
||||
}
|
||||
if (need_hints) {
|
||||
isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL,
|
||||
NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING,
|
||||
"no root hints for view '%s'",
|
||||
view->name);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Configure the view's transports (DoT/DoH)
|
||||
*/
|
||||
|
|
@ -5379,7 +5363,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
|||
obj = NULL;
|
||||
result = named_config_get(maps, "recursion", &obj);
|
||||
INSIST(result == ISC_R_SUCCESS);
|
||||
view->recursion = cfg_obj_asboolean(obj);
|
||||
view->recursion = (view->rdclass == dns_rdataclass_in &&
|
||||
cfg_obj_asboolean(obj));
|
||||
|
||||
obj = NULL;
|
||||
result = named_config_get(maps, "qname-minimization", &obj);
|
||||
|
|
@ -5479,14 +5464,13 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config,
|
|||
CHECK(configure_view_acl(vconfig, config, NULL, "allow-query-cache-on",
|
||||
NULL, actx, named_g_mctx, &view->cacheonacl));
|
||||
|
||||
if (strcmp(view->name, "_bind") != 0 &&
|
||||
view->rdclass != dns_rdataclass_chaos)
|
||||
{
|
||||
/* named.conf only */
|
||||
if (view->rdclass != dns_rdataclass_in) {
|
||||
dns_acl_none(named_g_mctx, &view->recursionacl);
|
||||
dns_acl_none(named_g_mctx, &view->recursiononacl);
|
||||
} else {
|
||||
CHECK(configure_view_acl(vconfig, config, NULL,
|
||||
"allow-recursion", NULL, actx,
|
||||
named_g_mctx, &view->recursionacl));
|
||||
/* named.conf only */
|
||||
CHECK(configure_view_acl(vconfig, config, NULL,
|
||||
"allow-recursion-on", NULL, actx,
|
||||
named_g_mctx, &view->recursiononacl));
|
||||
|
|
|
|||
|
|
@ -703,7 +703,7 @@ $DIG -p ${PORT} @10.53.1.2 d.normal.example a >dig.out.ns3.4.$n || ret=1
|
|||
grep 'recursion requested but not available' dig.out.ns3.4.$n >/dev/null || ret=1
|
||||
grep 'status: REFUSED' dig.out.ns3.4.$n >/dev/null || ret=1
|
||||
grep 'EDE: 18 (Prohibited)' dig.out.ns3.4.$n >/dev/null || ret=1
|
||||
nextpart ns3/named.run | grep 'allow-recursion-on did not match' >/dev/null || ret=1
|
||||
nextpart ns3/named.run | grep 'allow-query-cache-on did not match' >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
|
|
|
|||
|
|
@ -543,6 +543,7 @@ $CHECKCONF -l good.conf \
|
|||
| grep -v "is not implemented" \
|
||||
| grep -v "is not recommended" \
|
||||
| grep -v "no longer exists" \
|
||||
| grep -v "recursion will be disabled" \
|
||||
| grep -v "is obsolete" >checkconf.out$n || ret=1
|
||||
diff good.zonelist checkconf.out$n >diff.out$n || ret=1
|
||||
if [ $ret -ne 0 ]; then
|
||||
|
|
@ -819,5 +820,16 @@ if [ $ret != 0 ]; then
|
|||
fi
|
||||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "check 'recursion yes;' is warned and disabled in a non-IN view ($n)"
|
||||
ret=0
|
||||
$CHECKCONF warn-chaos-recursion.conf >checkconf.out$n 2>&1 || ret=1
|
||||
grep -F "recursion will be disabled" checkconf.out$n >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then
|
||||
echo_i "failed"
|
||||
ret=1
|
||||
fi
|
||||
status=$((status + ret))
|
||||
|
||||
echo_i "exit status: $status"
|
||||
[ $status -eq 0 ] || exit 1
|
||||
|
|
|
|||
12
bin/tests/system/checkconf/warn-chaos-recursion.conf
Normal file
12
bin/tests/system/checkconf/warn-chaos-recursion.conf
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
options {
|
||||
directory ".";
|
||||
};
|
||||
|
||||
view chaos ch {
|
||||
match-clients { any; };
|
||||
recursion yes;
|
||||
zone "." {
|
||||
type hint;
|
||||
file "chaos.hints";
|
||||
};
|
||||
};
|
||||
4
bin/tests/system/class/ns1/chaos.db.in
Normal file
4
bin/tests/system/class/ns1/chaos.db.in
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
. CH NS ns.root.
|
||||
ns.root. CH A ns.root. 1
|
||||
ns.root. CH AAAA \# 1 00
|
||||
|
||||
31
bin/tests/system/class/ns1/named.conf.j2
Normal file
31
bin/tests/system/class/ns1/named.conf.j2
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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; };
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
view chaos ch {
|
||||
match-clients { any; };
|
||||
recursion yes;
|
||||
zone "." {
|
||||
type hint;
|
||||
file "chaos.db";
|
||||
};
|
||||
zone "version.bind" {
|
||||
type primary;
|
||||
database "_builtin version";
|
||||
};
|
||||
};
|
||||
6
bin/tests/system/class/ns2/example.db.in
Normal file
6
bin/tests/system/class/ns2/example.db.in
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
$TTL 300
|
||||
@ CH SOA ns.example. hostmaster.example. 1 3600 1200 604800 300
|
||||
@ CH NS ns.example.
|
||||
ns CH TXT "ns"
|
||||
a CH A target.example. 1
|
||||
target CH TXT "target"
|
||||
11
bin/tests/system/class/ns2/localhost.db.in
Normal file
11
bin/tests/system/class/ns2/localhost.db.in
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
$ORIGIN 1.0.0.127.in-addr.arpa.
|
||||
$TTL 300
|
||||
@ IN SOA ns hostmaster 1 3600 900 604800 300
|
||||
@ IN NS ns
|
||||
ns IN A 127.0.0.1
|
||||
|
||||
@ IN KX 10 target.example.
|
||||
@ IN PX 10 map822.example. mapx400.example.
|
||||
@ IN NSAP 0x47000580ffff0000000001e133ffffff00016200
|
||||
@ IN NSAP-PTR target.example.
|
||||
@ in EID \# 01 aa
|
||||
42
bin/tests/system/class/ns2/named.conf.j2
Normal file
42
bin/tests/system/class/ns2/named.conf.j2
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
options {
|
||||
directory ".";
|
||||
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; };
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
view default {
|
||||
match-clients { any; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
zone "1.0.0.127.in-addr.arpa." {
|
||||
type primary;
|
||||
file "localhost.db";
|
||||
update-policy {
|
||||
grant * tcp-self . ANY;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
view chaos ch {
|
||||
match-clients { any; };
|
||||
recursion no;
|
||||
zone example {
|
||||
type primary;
|
||||
file "example.db";
|
||||
allow-update { any; };
|
||||
};
|
||||
};
|
||||
28
bin/tests/system/class/ns3/named.conf.j2
Normal file
28
bin/tests/system/class/ns3/named.conf.j2
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
options {
|
||||
directory ".";
|
||||
query-source address 10.53.0.3;
|
||||
notify-source 10.53.0.3;
|
||||
transfer-source 10.53.0.3;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.3; };
|
||||
listen-on-v6 { none; };
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
view chaos ch {
|
||||
match-clients { any; };
|
||||
recursion yes;
|
||||
dnssec-validation no;
|
||||
forward only;
|
||||
forwarders port @PORT@ { 10.53.0.2; };
|
||||
deny-answer-addresses { 0.0.0.0/0; ::/0; };
|
||||
};
|
||||
19
bin/tests/system/class/setup.sh
Normal file
19
bin/tests/system/class/setup.sh
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
# shellcheck source=conf.sh
|
||||
. ../conf.sh
|
||||
|
||||
cp ns1/chaos.db.in ns1/chaos.db
|
||||
cp ns2/example.db.in ns2/example.db
|
||||
cp ns2/localhost.db.in ns2/localhost.db
|
||||
54
bin/tests/system/class/tests_class_chaos.py
Normal file
54
bin/tests/system/class/tests_class_chaos.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# 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 dns.opcode
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"*/*.db",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_chaos_recursion():
|
||||
msg = isctest.query.create("foo.example.", "TXT", qclass="CH")
|
||||
res = isctest.query.udp(msg, "10.53.0.1")
|
||||
isctest.check.refused(res)
|
||||
|
||||
|
||||
def test_chaos_auth():
|
||||
msg = isctest.query.create("a.example.", "A", qclass="CH")
|
||||
res = isctest.query.udp(msg, "10.53.0.2")
|
||||
isctest.check.noerror(res)
|
||||
|
||||
|
||||
def test_chaos_forward():
|
||||
msg = isctest.query.create("a.example.", "A", qclass="CH")
|
||||
res = isctest.query.udp(msg, "10.53.0.3")
|
||||
isctest.check.refused(res)
|
||||
|
||||
|
||||
def test_chaos_notify():
|
||||
msg = isctest.query.create("example.", "SOA", qclass="CH", rd=False, dnssec=False)
|
||||
msg.set_opcode(dns.opcode.NOTIFY)
|
||||
msg.flags = dns.opcode.to_flags(dns.opcode.NOTIFY)
|
||||
res = isctest.query.udp(msg, "10.53.0.2")
|
||||
isctest.check.notimp(res)
|
||||
|
||||
|
||||
def test_query_class_none():
|
||||
msg = isctest.query.create("example.", "A", qclass="NONE")
|
||||
res = isctest.query.udp(msg, "10.53.0.2")
|
||||
isctest.check.formerr(res)
|
||||
137
bin/tests/system/class/tests_class_update.py
Normal file
137
bin/tests/system/class/tests_class_update.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# 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 socket
|
||||
import struct
|
||||
|
||||
from dns import message, rdataclass, rdatatype, update
|
||||
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"*/*.db",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def encode_name(name: str) -> bytes:
|
||||
out = b""
|
||||
for label in name.rstrip(".").split("."):
|
||||
out += bytes([len(label)]) + label.encode("ascii")
|
||||
return out + b"\x00"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"rdtype,rdclass,ttl,rdata",
|
||||
[
|
||||
(rdatatype.SRV, rdataclass.NONE, 0, b"\x00\x00\x00\x00\x00\x00\x01"),
|
||||
(rdatatype.SRV, rdataclass.NONE, 0, b"\x00"),
|
||||
(rdatatype.KX, rdataclass.NONE, 0, b""),
|
||||
(rdatatype.PX, rdataclass.NONE, 0, b""),
|
||||
(rdatatype.NSAP, rdataclass.NONE, 0, b""),
|
||||
(rdatatype.NSAP_PTR, rdataclass.NONE, 0, b""),
|
||||
(31, rdataclass.NONE, 0, b""), # dnspython doesn't define type EID
|
||||
],
|
||||
)
|
||||
def test_class_invalid(rdtype, rdclass, ttl, rdata, named_port):
|
||||
# these update messages are badly formatted, so we construct
|
||||
# them manually instead of using dnspython.
|
||||
|
||||
# opcode=UPDATE, 1 RRset in ZONE, 1 RRset in UPDATE
|
||||
header = struct.pack("!HHHHHH", 0, 0x2800, 1, 0, 1, 0)
|
||||
|
||||
# ZONE section: QNAME=<zone>, QTYPE=SOA, QCLASS=ANY
|
||||
zone_q = encode_name("1.0.0.127.in-addr.arpa") + struct.pack("!HH", 6, 255)
|
||||
|
||||
# UPDATE section RR:
|
||||
update_rr = (
|
||||
encode_name("1.0.0.127.in-addr.arpa")
|
||||
+ struct.pack("!HHIH", rdtype, rdclass, ttl, len(rdata))
|
||||
+ rdata
|
||||
)
|
||||
|
||||
m = header + zone_q + update_rr
|
||||
packet = struct.pack("!H", len(m)) + m
|
||||
|
||||
with socket.create_connection(
|
||||
("10.53.0.2", named_port), source_address=("127.0.0.1", 0), timeout=2.0
|
||||
) as s:
|
||||
s.sendall(packet)
|
||||
try:
|
||||
rwire = s.recv(4096)
|
||||
res = message.from_wire(rwire)
|
||||
isctest.check.formerr(res)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
pass
|
||||
|
||||
# check the server is answering
|
||||
msg = isctest.query.create("1.0.0.127.in-addr.arpa", "SRV")
|
||||
res = isctest.query.udp(msg, "10.53.0.2")
|
||||
isctest.check.noerror(res)
|
||||
isctest.check.rr_count_eq(res.answer, 0)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"rdtype,rdata",
|
||||
[
|
||||
(rdatatype.SVCB, "\\# 02 0000"),
|
||||
(rdatatype.WKS, "\\# 02 4142"),
|
||||
(rdatatype.WKS, "\\# 02 4344"),
|
||||
],
|
||||
)
|
||||
def test_class_chaosupdate(rdtype, rdata):
|
||||
up = update.UpdateMessage("example.", rdclass=rdataclass.CHAOS)
|
||||
up.add("foo.example.", 300, rdtype, rdata)
|
||||
res = isctest.query.tcp(up, "10.53.0.2")
|
||||
isctest.check.notimp(res)
|
||||
|
||||
|
||||
def test_class_undefined(ns2):
|
||||
up = update.UpdateMessage(".", rdclass=257)
|
||||
up.present(".", 0)
|
||||
up.answer[0].rdclass = rdataclass.NONE
|
||||
with ns2.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(up, "10.53.0.2")
|
||||
isctest.check.notimp(res)
|
||||
watcher.wait_for_line("invalid message class: CLASS257")
|
||||
|
||||
|
||||
def test_class_zero(ns2):
|
||||
up = update.UpdateMessage(".", rdclass=0)
|
||||
up.present(".", 0)
|
||||
up.answer[0].rdclass = rdataclass.NONE
|
||||
with ns2.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(up, "10.53.0.2")
|
||||
isctest.check.formerr(res)
|
||||
watcher.wait_for_line("message class could not be determined")
|
||||
|
||||
|
||||
def test_class_any(ns2):
|
||||
up = update.UpdateMessage(".", rdclass=rdataclass.ANY)
|
||||
up.present(".", 0)
|
||||
up.answer[0].rdclass = rdataclass.NONE
|
||||
with ns2.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(up, "10.53.0.2")
|
||||
isctest.check.formerr(res)
|
||||
watcher.wait_for_line("message parsing failed: FORMERR")
|
||||
|
||||
|
||||
def test_class_none(ns2):
|
||||
up = update.UpdateMessage(".", rdclass=rdataclass.NONE)
|
||||
up.present(".", 0)
|
||||
up.answer[0].rdclass = rdataclass.NONE
|
||||
with ns2.watch_log_from_here() as watcher:
|
||||
res = isctest.query.tcp(up, "10.53.0.2")
|
||||
isctest.check.formerr(res)
|
||||
watcher.wait_for_line("message parsing failed: FORMERR")
|
||||
|
|
@ -11,20 +11,9 @@ See the COPYRIGHT file distributed with this work for additional
|
|||
information regarding copyright ownership.
|
||||
"""
|
||||
|
||||
from collections.abc import AsyncGenerator, Callable, Coroutine, Sequence
|
||||
from dataclasses import dataclass, field
|
||||
from typing import (
|
||||
Any,
|
||||
AsyncGenerator,
|
||||
Callable,
|
||||
Coroutine,
|
||||
Dict,
|
||||
List,
|
||||
Optional,
|
||||
Set,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
from typing import Any, cast
|
||||
|
||||
import abc
|
||||
import asyncio
|
||||
|
|
@ -52,11 +41,10 @@ import dns.rdataset
|
|||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
import dns.tsig
|
||||
import dns.version
|
||||
import dns.zone
|
||||
|
||||
_UdpHandler = Callable[
|
||||
[bytes, Tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None]
|
||||
[bytes, tuple[str, int], asyncio.DatagramTransport], Coroutine[Any, Any, None]
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -74,7 +62,7 @@ class _AsyncUdpHandler(asyncio.DatagramProtocol):
|
|||
self,
|
||||
handler: _UdpHandler,
|
||||
) -> None:
|
||||
self._transport: Optional[asyncio.DatagramTransport] = None
|
||||
self._transport: asyncio.DatagramTransport | None = None
|
||||
self._handler: _UdpHandler = handler
|
||||
|
||||
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||
|
|
@ -83,7 +71,7 @@ class _AsyncUdpHandler(asyncio.DatagramProtocol):
|
|||
"""
|
||||
self._transport = cast(asyncio.DatagramTransport, transport)
|
||||
|
||||
def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None:
|
||||
def datagram_received(self, data: bytes, addr: tuple[str, int]) -> None:
|
||||
"""
|
||||
Called by asyncio when a datagram is received.
|
||||
"""
|
||||
|
|
@ -108,9 +96,9 @@ class AsyncServer:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
udp_handler: Optional[_UdpHandler],
|
||||
tcp_handler: Optional[_TcpHandler],
|
||||
pidfile: Optional[str] = None,
|
||||
udp_handler: _UdpHandler | None,
|
||||
tcp_handler: _TcpHandler | None,
|
||||
pidfile: str | None = None,
|
||||
) -> None:
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s %(levelname)8s %(message)s",
|
||||
|
|
@ -132,12 +120,12 @@ class AsyncServer:
|
|||
logging.info("Setting up IPv4 listener at %s:%d", ipv4_address, port)
|
||||
logging.info("Setting up IPv6 listener at [%s]:%d", ipv6_address, port)
|
||||
|
||||
self._ip_addresses: Tuple[str, str] = (ipv4_address, ipv6_address)
|
||||
self._ip_addresses: tuple[str, str] = (ipv4_address, ipv6_address)
|
||||
self._port: int = port
|
||||
self._udp_handler: Optional[_UdpHandler] = udp_handler
|
||||
self._tcp_handler: Optional[_TcpHandler] = tcp_handler
|
||||
self._pidfile: Optional[str] = pidfile
|
||||
self._work_done: Optional[asyncio.Future] = None
|
||||
self._udp_handler: _UdpHandler | None = udp_handler
|
||||
self._tcp_handler: _TcpHandler | None = tcp_handler
|
||||
self._pidfile: str | None = pidfile
|
||||
self._work_done: asyncio.Future | None = None
|
||||
|
||||
def _get_ipv4_address_from_directory_name(self) -> str:
|
||||
containing_directory = pathlib.Path().absolute().stem
|
||||
|
|
@ -185,7 +173,7 @@ class AsyncServer:
|
|||
loop.set_exception_handler(self._handle_exception)
|
||||
|
||||
def _handle_exception(
|
||||
self, _: asyncio.AbstractEventLoop, context: Dict[str, Any]
|
||||
self, _: asyncio.AbstractEventLoop, context: dict[str, Any]
|
||||
) -> None:
|
||||
assert self._work_done
|
||||
exception = context.get("exception", RuntimeError(context["message"]))
|
||||
|
|
@ -265,17 +253,16 @@ class QueryContext:
|
|||
|
||||
query: dns.message.Message
|
||||
response: dns.message.Message
|
||||
socket: Peer
|
||||
peer: Peer
|
||||
protocol: DnsProtocol
|
||||
zone: Optional[dns.zone.Zone] = field(default=None, init=False)
|
||||
soa: Optional[dns.rrset.RRset] = field(default=None, init=False)
|
||||
node: Optional[dns.node.Node] = field(default=None, init=False)
|
||||
answer: Optional[dns.rdataset.Rdataset] = field(default=None, init=False)
|
||||
alias: Optional[dns.name.Name] = field(default=None, init=False)
|
||||
_initialized_response: Optional[dns.message.Message] = field(
|
||||
default=None, init=False
|
||||
)
|
||||
_initialized_response_with_zone_data: Optional[dns.message.Message] = field(
|
||||
zone: dns.zone.Zone | None = field(default=None, init=False)
|
||||
soa: dns.rrset.RRset | None = field(default=None, init=False)
|
||||
node: dns.node.Node | None = field(default=None, init=False)
|
||||
answer: dns.rdataset.Rdataset | None = field(default=None, init=False)
|
||||
alias: dns.name.Name | None = field(default=None, init=False)
|
||||
_initialized_response: dns.message.Message | None = field(default=None, init=False)
|
||||
_initialized_response_with_zone_data: dns.message.Message | None = field(
|
||||
default=None, init=False
|
||||
)
|
||||
|
||||
|
|
@ -320,7 +307,7 @@ class ResponseAction(abc.ABC):
|
|||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
|
||||
async def perform(self) -> dns.message.Message | bytes | None:
|
||||
"""
|
||||
This method is expected to carry out arbitrary actions (e.g. wait for a
|
||||
specific amount of time, modify the answer, etc.) and then return the
|
||||
|
|
@ -343,14 +330,30 @@ class DnsResponseSend(ResponseAction):
|
|||
"""
|
||||
|
||||
response: dns.message.Message
|
||||
authoritative: Optional[bool] = None
|
||||
authoritative: bool | None = None
|
||||
delay: float = 0.0
|
||||
acknowledge_hand_rolled_response: bool = False
|
||||
|
||||
async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
|
||||
async def perform(self) -> dns.message.Message | bytes | None:
|
||||
"""
|
||||
Yield a potentially delayed response that is a dns.message.Message.
|
||||
"""
|
||||
assert isinstance(self.response, dns.message.Message)
|
||||
if not (
|
||||
_is_asyncserver_response(self.response)
|
||||
or self.acknowledge_hand_rolled_response
|
||||
):
|
||||
error = "The response you are trying to send was not created using "
|
||||
error += "AsyncDnsServer's response preparation methods. "
|
||||
error += "This will break features such as automatic AA flag "
|
||||
error += "and RCODE handling. If you need a fresh copy of a "
|
||||
error += "response, use `QueryContext.prepare_new_response` "
|
||||
error += "instead of `dns.message.make_response`. "
|
||||
error += "To acknowledge this and proceed anyway, set "
|
||||
error += "`acknowledge_hand_rolled_response=True` in "
|
||||
error += "DnsResponseSend's constructor."
|
||||
raise RuntimeError(error)
|
||||
|
||||
if self.authoritative is not None:
|
||||
if self.authoritative:
|
||||
self.response.flags |= dns.flags.AA
|
||||
|
|
@ -377,7 +380,7 @@ class BytesResponseSend(ResponseAction):
|
|||
response: bytes
|
||||
delay: float = 0.0
|
||||
|
||||
async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
|
||||
async def perform(self) -> dns.message.Message | bytes | None:
|
||||
"""
|
||||
Yield a potentially delayed response that is a sequence of bytes.
|
||||
"""
|
||||
|
|
@ -394,7 +397,7 @@ class ResponseDrop(ResponseAction):
|
|||
Action which does nothing - as if a packet was dropped.
|
||||
"""
|
||||
|
||||
async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
|
||||
async def perform(self) -> dns.message.Message | bytes | None:
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -403,17 +406,16 @@ class _ConnectionTeardownRequested(Exception):
|
|||
|
||||
|
||||
@dataclass
|
||||
class ResponseDropAndCloseConnection(ResponseAction):
|
||||
class CloseConnection(ResponseAction):
|
||||
"""
|
||||
Action which makes the server close the connection after the DNS query is
|
||||
received by the server (TCP only).
|
||||
Action which makes the server close the connection (TCP only).
|
||||
|
||||
The connection may be closed with a delay if requested.
|
||||
"""
|
||||
|
||||
delay: float = 0.0
|
||||
|
||||
async def perform(self) -> Optional[Union[dns.message.Message, bytes]]:
|
||||
async def perform(self) -> dns.message.Message | bytes | None:
|
||||
if self.delay > 0:
|
||||
logging.info("Waiting %.1fs before closing TCP connection", self.delay)
|
||||
await asyncio.sleep(self.delay)
|
||||
|
|
@ -495,7 +497,7 @@ class IgnoreAllConnections(ConnectionHandler):
|
|||
client socket, effectively ignoring all incoming connections.
|
||||
"""
|
||||
|
||||
_connections: Set[asyncio.StreamWriter] = field(default_factory=set)
|
||||
_connections: set[asyncio.StreamWriter] = field(default_factory=set)
|
||||
|
||||
async def handle(
|
||||
self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, peer: Peer
|
||||
|
|
@ -529,8 +531,8 @@ class ConnectionReset(ConnectionHandler):
|
|||
make the server send an RST segment; this happens when the server closes a
|
||||
client's socket while there is still unread data in that socket's buffer.
|
||||
If closing the connection _after_ the query is read by the server is enough
|
||||
for a given use case, the ResponseDropAndCloseConnection response handler
|
||||
should be used instead.
|
||||
for a given use case, the CloseConnection response handler should be used
|
||||
instead.
|
||||
"""
|
||||
|
||||
delay: float = 0.0
|
||||
|
|
@ -606,14 +608,14 @@ class QnameHandler(ResponseHandler):
|
|||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def qnames(self) -> List[str]:
|
||||
def qnames(self) -> list[str]:
|
||||
"""
|
||||
A list of QNAMEs handled by this class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._qnames: List[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames]
|
||||
self._qnames: list[dns.name.Name] = [dns.name.from_text(d) for d in self.qnames]
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)})"
|
||||
|
|
@ -626,6 +628,105 @@ class QnameHandler(ResponseHandler):
|
|||
return qctx.qname in self._qnames
|
||||
|
||||
|
||||
class QnameQtypeHandler(QnameHandler):
|
||||
"""
|
||||
Handle queries for which both of the following conditions are true:
|
||||
|
||||
- the query's QNAME is present in `self.qnames`,
|
||||
- the query's QTYPE is present in `self.qtypes`.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def qtypes(self) -> list[dns.rdatatype.RdataType]:
|
||||
"""
|
||||
A list of QTYPEs handled by this class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._qtypes: list[dns.rdatatype.RdataType] = self.qtypes
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(QNAMEs: {', '.join(self.qnames)}; QTYPEs: {', '.join(map(str, self.qtypes))})"
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
"""
|
||||
Handle queries whose QNAME and QTYPE match any of the QNAMEs and
|
||||
QTYPEs handled by this class.
|
||||
"""
|
||||
return qctx.qtype in self._qtypes and super().match(qctx)
|
||||
|
||||
|
||||
class StaticResponseHandler(ResponseHandler):
|
||||
"""
|
||||
Base class used for deriving custom static response handlers.
|
||||
|
||||
The derived class can specify the RRsets to be included in the answer,
|
||||
authority, and additional sections of the response, whether to set the AA
|
||||
bit in the response, and a delay before sending the response.
|
||||
|
||||
The default implementation of `get_responses()` uses these properties to
|
||||
prepare and yield a single response.
|
||||
"""
|
||||
|
||||
@property
|
||||
def rcode(self) -> dns.rcode.Rcode | None:
|
||||
"""
|
||||
Optional RCODE to be set in the response.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def answer(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the answer section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def authority(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the authority section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def additional(self) -> Sequence[dns.rrset.RRset]:
|
||||
"""
|
||||
RRsets to be included in the additional section of the response.
|
||||
"""
|
||||
return []
|
||||
|
||||
@property
|
||||
def authoritative(self) -> bool | None:
|
||||
"""
|
||||
Whether to set the AA bit in the response.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def delay(self) -> float:
|
||||
"""
|
||||
Delay before sending the response.
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
qctx.prepare_new_response(with_zone_data=False)
|
||||
qctx.response.answer.extend(self.answer)
|
||||
qctx.response.authority.extend(self.authority)
|
||||
qctx.response.additional.extend(self.additional)
|
||||
if self.rcode is not None:
|
||||
qctx.response.set_rcode(self.rcode)
|
||||
yield DnsResponseSend(
|
||||
qctx.response, authoritative=self.authoritative, delay=self.delay
|
||||
)
|
||||
|
||||
|
||||
class DomainHandler(ResponseHandler):
|
||||
"""
|
||||
Base class used for deriving custom domain handlers.
|
||||
|
|
@ -633,20 +734,28 @@ class DomainHandler(ResponseHandler):
|
|||
The derived class must specify a list of `domains` that it wants to handle.
|
||||
Queries for any of these domains (and their subdomains) will then be passed
|
||||
to the `get_response()` method in the derived class.
|
||||
|
||||
The most specific matching domain is stored in the `matched_domain` attribute.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def domains(self) -> List[str]:
|
||||
def domains(self) -> list[str]:
|
||||
"""
|
||||
A list of domain names handled by this class.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._domains: List[dns.name.Name] = [
|
||||
dns.name.from_text(d) for d in self.domains
|
||||
]
|
||||
self._domains: list[dns.name.Name] = sorted(
|
||||
[dns.name.from_text(d) for d in self.domains], reverse=True
|
||||
)
|
||||
self._matched_domain: dns.name.Name | None = None
|
||||
|
||||
@property
|
||||
def matched_domain(self) -> dns.name.Name:
|
||||
assert self._matched_domain is not None
|
||||
return self._matched_domain
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(domains: {', '.join(self.domains)})"
|
||||
|
|
@ -656,20 +765,124 @@ class DomainHandler(ResponseHandler):
|
|||
Handle queries whose QNAME matches any of the domains handled by this
|
||||
class.
|
||||
"""
|
||||
self._matched_domain = None
|
||||
for domain in self._domains:
|
||||
if qctx.qname.is_subdomain(domain):
|
||||
self._matched_domain = domain
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class ForwarderHandler(ResponseHandler):
|
||||
"""
|
||||
A handler forwarding all received queries to another DNS server with an
|
||||
optional delay and then relaying the responses back to the original client.
|
||||
|
||||
Queries are currently always forwarded via UDP.
|
||||
"""
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def target(self) -> str:
|
||||
"""
|
||||
The address of the DNS server to forward queries to.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def port(self) -> int:
|
||||
"""
|
||||
The port of the DNS server to forward queries to.
|
||||
|
||||
The default value of 0 causes the same port as the one used by this
|
||||
server for listening to be used.
|
||||
"""
|
||||
return 0
|
||||
|
||||
@property
|
||||
def delay(self) -> float:
|
||||
"""
|
||||
The number of seconds to wait before forwarding each query.
|
||||
"""
|
||||
return 0.0
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(target: {self.target}:{self.port})"
|
||||
|
||||
class ForwarderProtocol(asyncio.DatagramProtocol):
|
||||
def __init__(self, query: bytes, response: asyncio.Future) -> None:
|
||||
self._query = query
|
||||
self._response = response
|
||||
|
||||
def connection_made(self, transport: asyncio.BaseTransport) -> None:
|
||||
logging.debug("[OUT] %s", self._query.hex())
|
||||
cast(asyncio.DatagramTransport, transport).sendto(self._query)
|
||||
|
||||
def datagram_received(self, data: bytes, _: tuple[str, int]) -> None:
|
||||
logging.debug("[IN] %s", data.hex())
|
||||
self._response.set_result(data)
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[ResponseAction, None]:
|
||||
loop = asyncio.get_running_loop()
|
||||
response = loop.create_future()
|
||||
forwarding_target = f"{self.target}:{self.port or qctx.socket.port}"
|
||||
|
||||
if self.delay > 0:
|
||||
logging.info(
|
||||
"Waiting %.1fs before forwarding %s query from %s to %s over UDP",
|
||||
self.delay,
|
||||
qctx.protocol.name,
|
||||
qctx.peer,
|
||||
forwarding_target,
|
||||
)
|
||||
await asyncio.sleep(self.delay)
|
||||
|
||||
logging.info(
|
||||
"Forwarding %s query from %s to %s over UDP",
|
||||
qctx.protocol.name,
|
||||
qctx.peer,
|
||||
forwarding_target,
|
||||
)
|
||||
|
||||
transport, _ = await loop.create_datagram_endpoint(
|
||||
lambda: self.ForwarderProtocol(qctx.query.to_wire(), response),
|
||||
local_addr=(qctx.socket.host, 0),
|
||||
remote_addr=(self.target, self.port or qctx.socket.port),
|
||||
)
|
||||
|
||||
try:
|
||||
await response
|
||||
finally:
|
||||
transport.close()
|
||||
|
||||
logging.info(
|
||||
"Relaying UDP response from %s to %s over %s",
|
||||
forwarding_target,
|
||||
qctx.peer,
|
||||
qctx.protocol.name,
|
||||
)
|
||||
|
||||
try:
|
||||
message = _DnsMessageWithTsigDisabled.from_wire(response.result())
|
||||
yield DnsResponseSend(message, acknowledge_hand_rolled_response=True)
|
||||
except dns.exception.DNSException:
|
||||
logging.warning(
|
||||
"Failed to parse response from %s as a DNS message, relaying it as raw bytes",
|
||||
forwarding_target,
|
||||
)
|
||||
yield BytesResponseSend(response.result())
|
||||
|
||||
|
||||
@dataclass
|
||||
class _ZoneTreeNode:
|
||||
"""
|
||||
A node representing a zone with one origin.
|
||||
"""
|
||||
|
||||
zone: Optional[dns.zone.Zone]
|
||||
children: List["_ZoneTreeNode"] = field(default_factory=list)
|
||||
zone: dns.zone.Zone | None
|
||||
children: list["_ZoneTreeNode"] = field(default_factory=list)
|
||||
|
||||
|
||||
class _ZoneTree:
|
||||
|
|
@ -719,7 +932,7 @@ class _ZoneTree:
|
|||
node_from.children.remove(child)
|
||||
node_to.children.append(child)
|
||||
|
||||
def find_best_zone(self, name: dns.name.Name) -> Optional[dns.zone.Zone]:
|
||||
def find_best_zone(self, name: dns.name.Name) -> dns.zone.Zone | None:
|
||||
"""
|
||||
Return the closest matching zone (if any) for the domain name.
|
||||
"""
|
||||
|
|
@ -737,7 +950,7 @@ class _DnsMessageWithTsigDisabled(dns.message.Message):
|
|||
"""
|
||||
|
||||
class _DisableTsigHandling(contextlib.ContextDecorator):
|
||||
def __init__(self, message: Optional[dns.message.Message] = None) -> None:
|
||||
def __init__(self, message: dns.message.Message | None = None) -> None:
|
||||
self.original_tsig_sign = dns.tsig.sign
|
||||
self.original_tsig_validate = dns.tsig.validate
|
||||
if message:
|
||||
|
|
@ -749,7 +962,7 @@ class _DnsMessageWithTsigDisabled(dns.message.Message):
|
|||
from failing on messages initialized with `dns.message.from_wire(keyring=False)`.
|
||||
"""
|
||||
|
||||
def sign(*_: Any, **__: Any) -> Tuple[dns.rdata.Rdata, None]:
|
||||
def sign(*_: Any, **__: Any) -> tuple[dns.rdata.Rdata, None]:
|
||||
assert self.tsig
|
||||
return self.tsig[0], None
|
||||
|
||||
|
|
@ -792,6 +1005,19 @@ class _NoKeyringType:
|
|||
pass
|
||||
|
||||
|
||||
_ASYNCSERVER_RESPONSE_MARKER = "__is_asyncserver_response__"
|
||||
|
||||
|
||||
def _make_asyncserver_response(query: dns.message.Message) -> dns.message.Message:
|
||||
response = dns.message.make_response(query)
|
||||
setattr(response, _ASYNCSERVER_RESPONSE_MARKER, True)
|
||||
return response
|
||||
|
||||
|
||||
def _is_asyncserver_response(message: dns.message.Message) -> bool:
|
||||
return getattr(message, _ASYNCSERVER_RESPONSE_MARKER, False)
|
||||
|
||||
|
||||
class AsyncDnsServer(AsyncServer):
|
||||
"""
|
||||
DNS server which responds to queries based on zone data and/or custom
|
||||
|
|
@ -812,17 +1038,17 @@ class AsyncDnsServer(AsyncServer):
|
|||
self,
|
||||
/,
|
||||
default_rcode: dns.rcode.Rcode = dns.rcode.REFUSED,
|
||||
default_aa: bool = True,
|
||||
keyring: Union[
|
||||
Dict[dns.name.Name, dns.tsig.Key], None, _NoKeyringType
|
||||
] = _NoKeyringType(),
|
||||
default_aa: bool = False,
|
||||
keyring: (
|
||||
dict[dns.name.Name, dns.tsig.Key] | None | _NoKeyringType
|
||||
) = _NoKeyringType(),
|
||||
acknowledge_manual_dname_handling: bool = False,
|
||||
) -> None:
|
||||
super().__init__(self._handle_udp, self._handle_tcp, "ans.pid")
|
||||
|
||||
self._zone_tree: _ZoneTree = _ZoneTree()
|
||||
self._connection_handler: Optional[ConnectionHandler] = None
|
||||
self._response_handlers: List[ResponseHandler] = []
|
||||
self._connection_handler: ConnectionHandler | None = None
|
||||
self._response_handlers: list[ResponseHandler] = []
|
||||
self._default_rcode = default_rcode
|
||||
self._default_aa = default_aa
|
||||
self._keyring = keyring
|
||||
|
|
@ -849,10 +1075,18 @@ class AsyncDnsServer(AsyncServer):
|
|||
else:
|
||||
self._response_handlers.append(handler)
|
||||
|
||||
def install_response_handlers(self, handlers: List[ResponseHandler]) -> None:
|
||||
def install_response_handlers(self, *handlers: ResponseHandler) -> None:
|
||||
for handler in handlers:
|
||||
self.install_response_handler(handler)
|
||||
|
||||
def replace_response_handlers(self, *new_handlers: ResponseHandler) -> None:
|
||||
"""
|
||||
Uninstall all currently installed handlers and install the provided ones.
|
||||
"""
|
||||
logging.info("Uninstalling response handlers: %s", str(self._response_handlers))
|
||||
self._response_handlers.clear()
|
||||
self.install_response_handlers(*new_handlers)
|
||||
|
||||
def uninstall_response_handler(self, handler: ResponseHandler) -> None:
|
||||
"""
|
||||
Remove the specified handler from the list of response handlers.
|
||||
|
|
@ -923,11 +1157,13 @@ class AsyncDnsServer(AsyncServer):
|
|||
raise ValueError(error)
|
||||
|
||||
async def _handle_udp(
|
||||
self, wire: bytes, addr: Tuple[str, int], transport: asyncio.DatagramTransport
|
||||
self, wire: bytes, addr: tuple[str, int], transport: asyncio.DatagramTransport
|
||||
) -> None:
|
||||
logging.debug("Received UDP message: %s", wire.hex())
|
||||
socket_info = transport.get_extra_info("sockname")
|
||||
socket = Peer(socket_info[0], socket_info[1])
|
||||
peer = Peer(addr[0], addr[1])
|
||||
responses = self._handle_query(wire, peer, DnsProtocol.UDP)
|
||||
responses = self._handle_query(wire, socket, peer, DnsProtocol.UDP)
|
||||
async for response in responses:
|
||||
logging.debug("Sending UDP message: %s", response.hex())
|
||||
transport.sendto(response, addr)
|
||||
|
|
@ -964,7 +1200,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
|
||||
async def _read_tcp_query(
|
||||
self, reader: asyncio.StreamReader, peer: Peer
|
||||
) -> Optional[bytes]:
|
||||
) -> bytes | None:
|
||||
wire_length = await self._read_tcp_query_wire_length(reader, peer)
|
||||
if not wire_length:
|
||||
return None
|
||||
|
|
@ -973,7 +1209,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
|
||||
async def _read_tcp_query_wire_length(
|
||||
self, reader: asyncio.StreamReader, peer: Peer
|
||||
) -> Optional[int]:
|
||||
) -> int | None:
|
||||
logging.debug("Receiving TCP message length from %s...", peer)
|
||||
|
||||
wire_length_bytes = await self._read_tcp_octets(reader, peer, 2)
|
||||
|
|
@ -986,7 +1222,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
|
||||
async def _read_tcp_query_wire(
|
||||
self, reader: asyncio.StreamReader, peer: Peer, wire_length: int
|
||||
) -> Optional[bytes]:
|
||||
) -> bytes | None:
|
||||
logging.debug("Receiving TCP message (%d octets) from %s...", wire_length, peer)
|
||||
|
||||
wire = await self._read_tcp_octets(reader, peer, wire_length)
|
||||
|
|
@ -999,7 +1235,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
|
||||
async def _read_tcp_octets(
|
||||
self, reader: asyncio.StreamReader, peer: Peer, expected: int
|
||||
) -> Optional[bytes]:
|
||||
) -> bytes | None:
|
||||
buffer = b""
|
||||
|
||||
while len(buffer) < expected:
|
||||
|
|
@ -1024,39 +1260,39 @@ class AsyncDnsServer(AsyncServer):
|
|||
async def _send_tcp_response(
|
||||
self, writer: asyncio.StreamWriter, peer: Peer, wire: bytes
|
||||
) -> None:
|
||||
responses = self._handle_query(wire, peer, DnsProtocol.TCP)
|
||||
socket_info = writer.get_extra_info("sockname")
|
||||
socket = Peer(socket_info[0], socket_info[1])
|
||||
responses = self._handle_query(wire, socket, peer, DnsProtocol.TCP)
|
||||
async for response in responses:
|
||||
logging.debug("Sending TCP response: %s", response.hex())
|
||||
writer.write(response)
|
||||
await writer.drain()
|
||||
|
||||
def _log_query(self, qctx: QueryContext, peer: Peer, protocol: DnsProtocol) -> None:
|
||||
def _log_query(self, qctx: QueryContext) -> None:
|
||||
logging.info(
|
||||
"Received %s/%s/%s (ID=%d) query from %s (%s)",
|
||||
"Received %s/%s/%s (ID=%d) query from %s on %s (%s)",
|
||||
qctx.qname.to_text(omit_final_dot=True),
|
||||
dns.rdataclass.to_text(qctx.qclass),
|
||||
dns.rdatatype.to_text(qctx.qtype),
|
||||
qctx.query.id,
|
||||
peer,
|
||||
protocol.name,
|
||||
qctx.peer,
|
||||
qctx.socket,
|
||||
qctx.protocol.name,
|
||||
)
|
||||
logging.debug(
|
||||
"\n".join([f"[IN] {l}" for l in [""] + str(qctx.query).splitlines()])
|
||||
)
|
||||
|
||||
def _log_response(
|
||||
self,
|
||||
qctx: QueryContext,
|
||||
response: Optional[Union[dns.message.Message, bytes]],
|
||||
peer: Peer,
|
||||
protocol: DnsProtocol,
|
||||
self, qctx: QueryContext, response: dns.message.Message | bytes | None
|
||||
) -> None:
|
||||
if not response:
|
||||
logging.info(
|
||||
"Not sending a response to query (ID=%d) from %s (%s)",
|
||||
"Not sending a response to query (ID=%d) from %s on %s (%s)",
|
||||
qctx.query.id,
|
||||
peer,
|
||||
protocol.name,
|
||||
qctx.peer,
|
||||
qctx.socket,
|
||||
qctx.protocol.name,
|
||||
)
|
||||
return
|
||||
|
||||
|
|
@ -1071,7 +1307,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
qtype = "-"
|
||||
|
||||
logging.info(
|
||||
"Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s (%s)",
|
||||
"Sending %s/%s/%s (ID=%d) response (%d/%d/%d/%d) to a query (ID=%d) from %s on %s (%s)",
|
||||
qname,
|
||||
qclass,
|
||||
qtype,
|
||||
|
|
@ -1081,8 +1317,9 @@ class AsyncDnsServer(AsyncServer):
|
|||
len(response.authority),
|
||||
len(response.additional),
|
||||
qctx.query.id,
|
||||
peer,
|
||||
protocol.name,
|
||||
qctx.peer,
|
||||
qctx.socket,
|
||||
qctx.protocol.name,
|
||||
)
|
||||
logging.debug(
|
||||
"\n".join([f"[OUT] {l}" for l in [""] + str(response).splitlines()])
|
||||
|
|
@ -1090,16 +1327,17 @@ class AsyncDnsServer(AsyncServer):
|
|||
return
|
||||
|
||||
logging.info(
|
||||
"Sending response (%d bytes) to a query (ID=%d) from %s (%s)",
|
||||
"Sending response (%d bytes) to a query (ID=%d) from %s on %s (%s)",
|
||||
len(response),
|
||||
qctx.query.id,
|
||||
peer,
|
||||
protocol.name,
|
||||
qctx.peer,
|
||||
qctx.socket,
|
||||
qctx.protocol.name,
|
||||
)
|
||||
logging.debug("[OUT] %s", response.hex())
|
||||
|
||||
async def _handle_query(
|
||||
self, wire: bytes, peer: Peer, protocol: DnsProtocol
|
||||
self, wire: bytes, socket: Peer, peer: Peer, protocol: DnsProtocol
|
||||
) -> AsyncGenerator[bytes, None]:
|
||||
"""
|
||||
Yield wire data to send as a response over the established transport.
|
||||
|
|
@ -1109,12 +1347,12 @@ class AsyncDnsServer(AsyncServer):
|
|||
except dns.exception.DNSException as exc:
|
||||
logging.error("Invalid query from %s (%s): %s", peer, wire.hex(), exc)
|
||||
return
|
||||
response_stub = dns.message.make_response(query)
|
||||
qctx = QueryContext(query, response_stub, peer, protocol)
|
||||
self._log_query(qctx, peer, protocol)
|
||||
response_stub = _make_asyncserver_response(query)
|
||||
qctx = QueryContext(query, response_stub, socket, peer, protocol)
|
||||
self._log_query(qctx)
|
||||
responses = self._prepare_responses(qctx)
|
||||
async for response in responses:
|
||||
self._log_response(qctx, response, peer, protocol)
|
||||
self._log_response(qctx, response)
|
||||
if response:
|
||||
if isinstance(response, dns.message.Message):
|
||||
response = response.to_wire(max_size=65535)
|
||||
|
|
@ -1146,7 +1384,7 @@ class AsyncDnsServer(AsyncServer):
|
|||
|
||||
async def _prepare_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]:
|
||||
) -> AsyncGenerator[dns.message.Message | bytes | None, None]:
|
||||
"""
|
||||
Yield response(s) either from response handlers or zone data.
|
||||
"""
|
||||
|
|
@ -1339,10 +1577,10 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
|
|||
return dns.name.from_text(self._CONTROL_DOMAIN)
|
||||
|
||||
@functools.cached_property
|
||||
def _commands(self) -> Dict[dns.name.Name, "ControlCommand"]:
|
||||
def _commands(self) -> dict[dns.name.Name, "ControlCommand"]:
|
||||
return {}
|
||||
|
||||
def install_control_commands(self, commands: List["ControlCommand"]) -> None:
|
||||
def install_control_commands(self, *commands: "ControlCommand") -> None:
|
||||
for command in commands:
|
||||
self.install_control_command(command)
|
||||
|
||||
|
|
@ -1360,7 +1598,7 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
|
|||
|
||||
async def _prepare_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[Optional[Union[dns.message.Message, bytes]], None]:
|
||||
) -> AsyncGenerator[dns.message.Message | bytes | None, None]:
|
||||
"""
|
||||
Detect and handle control queries, falling back to normal processing
|
||||
for non-control queries.
|
||||
|
|
@ -1373,9 +1611,7 @@ class ControllableAsyncDnsServer(AsyncDnsServer):
|
|||
async for response in super()._prepare_responses(qctx):
|
||||
yield response
|
||||
|
||||
def _handle_control_command(
|
||||
self, qctx: QueryContext
|
||||
) -> Optional[dns.message.Message]:
|
||||
def _handle_control_command(self, qctx: QueryContext) -> dns.message.Message | None:
|
||||
"""
|
||||
Detect and handle control queries.
|
||||
|
||||
|
|
@ -1450,8 +1686,8 @@ class ControlCommand(abc.ABC):
|
|||
|
||||
@abc.abstractmethod
|
||||
def handle(
|
||||
self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext
|
||||
) -> Optional[str]:
|
||||
self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
|
||||
) -> str | None:
|
||||
"""
|
||||
This method is expected to carry out arbitrary actions in response to a
|
||||
control query. Note that it is invoked synchronously (it is not a
|
||||
|
|
@ -1489,11 +1725,11 @@ class ToggleResponsesCommand(ControlCommand):
|
|||
control_subdomain = "send-responses"
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._current_handler: Optional[IgnoreAllQueries] = None
|
||||
self._current_handler: IgnoreAllQueries | None = None
|
||||
|
||||
def handle(
|
||||
self, args: List[str], server: ControllableAsyncDnsServer, qctx: QueryContext
|
||||
) -> Optional[str]:
|
||||
self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
|
||||
) -> str | None:
|
||||
if len(args) != 1:
|
||||
logging.error("Invalid %s query %s", self, qctx.qname)
|
||||
qctx.response.set_rcode(dns.rcode.SERVFAIL)
|
||||
|
|
@ -1518,3 +1754,30 @@ class ToggleResponsesCommand(ControlCommand):
|
|||
logging.error("Unrecognized response sending mode '%s'", mode)
|
||||
qctx.response.set_rcode(dns.rcode.SERVFAIL)
|
||||
return f"unrecognized response sending mode '{mode}'"
|
||||
|
||||
|
||||
class SwitchControlCommand(ControlCommand):
|
||||
"""
|
||||
Switch the server's response handlers based on the control query.
|
||||
|
||||
A sequence of response handlers is associated with each key. When a
|
||||
control query is received, the server's response handlers are replaced
|
||||
with the sequence associated with the key extracted from the control
|
||||
query.
|
||||
"""
|
||||
|
||||
control_subdomain = "switch"
|
||||
|
||||
def __init__(self, handler_mapping: dict[str, Sequence[ResponseHandler]]):
|
||||
self._handler_mapping = handler_mapping
|
||||
|
||||
def handle(
|
||||
self, args: list[str], server: ControllableAsyncDnsServer, qctx: QueryContext
|
||||
) -> str | None:
|
||||
if len(args) != 1 or args[0] not in self._handler_mapping:
|
||||
logging.error("Invalid %s query %s", self, qctx.qname)
|
||||
qctx.response.set_rcode(dns.rcode.SERVFAIL)
|
||||
return f"invalid query; exactly one of {list(self._handler_mapping.keys())} is expected in QNAME"
|
||||
|
||||
server.replace_response_handlers(*self._handler_mapping[args[0]])
|
||||
return f"switched to handler set '{args[0]}'"
|
||||
|
|
|
|||
|
|
@ -42,6 +42,10 @@ def servfail(message: dns.message.Message) -> None:
|
|||
rcode(message, dns.rcode.SERVFAIL)
|
||||
|
||||
|
||||
def formerr(message: dns.message.Message) -> None:
|
||||
rcode(message, dns.rcode.FORMERR)
|
||||
|
||||
|
||||
def adflag(message: dns.message.Message) -> None:
|
||||
assert (message.flags & dns.flags.AD) != 0, str(message)
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ def create(
|
|||
qtype,
|
||||
qclass=dns.rdataclass.IN,
|
||||
dnssec: bool = True,
|
||||
rd: bool = True,
|
||||
cd: bool = False,
|
||||
ad: bool = True,
|
||||
) -> dns.message.Message:
|
||||
|
|
@ -113,7 +114,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:
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ update.nil IN SOA ns1.example.nil. hostmaster.example.nil. (
|
|||
3600 ; minimum (1 hour)
|
||||
)
|
||||
update.nil. NS ns1.update.nil.
|
||||
update.nil. KX 0 .
|
||||
ns1.update.nil. A 10.53.0.2
|
||||
ns2.update.nil. AAAA ::1
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -340,8 +340,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
|
|||
n=$((n + 1))
|
||||
ret=0
|
||||
echo_i "check that TYPE=0 update is handled ($n)"
|
||||
nextpart ns1/named.run >/dev/null
|
||||
echo "a0e4280000010000000100000000060001c00c000000fe000000000000" \
|
||||
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
|
||||
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
|
||||
wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
|
||||
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
|
||||
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
|
||||
[ $ret = 0 ] || {
|
||||
|
|
@ -352,20 +354,10 @@ grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
|
|||
n=$((n + 1))
|
||||
ret=0
|
||||
echo_i "check that TYPE=0 additional data is handled ($n)"
|
||||
nextpart ns1/named.run >/dev/null
|
||||
echo "a0e4280000010000000000010000060001c00c000000fe000000000000" \
|
||||
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
|
||||
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
|
||||
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
|
||||
[ $ret = 0 ] || {
|
||||
echo_i "failed"
|
||||
status=1
|
||||
}
|
||||
|
||||
n=$((n + 1))
|
||||
ret=0
|
||||
echo_i "check that update to undefined class is handled ($n)"
|
||||
echo "a0e4280000010001000000000000060101c00c000000fe000000000000" \
|
||||
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp >/dev/null || ret=1
|
||||
| $PERL ../packet.pl -a 10.53.0.1 -p ${PORT} -t tcp -b >/dev/null || ret=1
|
||||
wait_for_log 2 "message parsing failed: FORMERR" ns1/named.run || ret=1
|
||||
$DIG $DIGOPTS +tcp version.bind txt ch @10.53.0.1 >dig.out.ns1.$n || ret=1
|
||||
grep "status: NOERROR" dig.out.ns1.$n >/dev/null || ret=1
|
||||
[ $ret = 0 ] || {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
# -p <port>: specify port
|
||||
# -t <protocol>: specify UDP or TCP
|
||||
# -r <num>: send packet <num> times
|
||||
# -b: blocking io
|
||||
# -d: dump response packets
|
||||
#
|
||||
# If not specified, address defaults to 127.0.0.1, port to 53, protocol
|
||||
|
|
@ -51,6 +52,8 @@ use strict;
|
|||
use Getopt::Std;
|
||||
use IO::File;
|
||||
use IO::Socket;
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
|
||||
sub usage {
|
||||
print ("Usage: packet.pl [-a address] [-d] [-p port] [-t (tcp|udp)] [-r <repeats>] [file]\n");
|
||||
|
|
@ -61,8 +64,6 @@ my $sock;
|
|||
my $proto;
|
||||
|
||||
sub dumppacket {
|
||||
use Net::DNS;
|
||||
use Net::DNS::Packet;
|
||||
|
||||
my $rin;
|
||||
my $rout;
|
||||
|
|
@ -96,7 +97,7 @@ sub dumppacket {
|
|||
}
|
||||
|
||||
my %options={};
|
||||
getopts("a:dp:t:r:", \%options);
|
||||
getopts("a:bdp:t:r:", \%options);
|
||||
|
||||
my $addr = "127.0.0.1";
|
||||
$addr = $options{a} if defined $options{a};
|
||||
|
|
@ -111,6 +112,8 @@ usage if ($proto !~ /^(udp|tcp)$/);
|
|||
my $repeats = 1;
|
||||
$repeats = $options{r} if defined $options{r};
|
||||
|
||||
my $blocking = defined $options{b} ? 1 : 0;
|
||||
|
||||
my $file = "STDIN";
|
||||
if (@ARGV >= 1) {
|
||||
my $filename = shift @ARGV;
|
||||
|
|
@ -132,8 +135,22 @@ my $len = length $data;
|
|||
my $output = unpack("H*", $data);
|
||||
print ("sending $repeats time(s): $output\n");
|
||||
|
||||
|
||||
if (defined $options{d}) {
|
||||
my $request;
|
||||
if ($Net::DNS::VERSION > 0.68) {
|
||||
$request = new Net::DNS::Packet(\$data, 0);
|
||||
$@ and die $@;
|
||||
} else {
|
||||
my $err;
|
||||
($request, $err) = new Net::DNS::Packet(\$data, 0);
|
||||
$err and die $err;
|
||||
}
|
||||
$request->print;
|
||||
}
|
||||
|
||||
$sock = IO::Socket::INET->new(PeerAddr => $addr, PeerPort => $port,
|
||||
Blocking => 0,
|
||||
Blocking => $blocking,
|
||||
Proto => $proto,) or die "$!";
|
||||
|
||||
STDOUT->autoflush(1);
|
||||
|
|
|
|||
126
bin/tests/system/resend_loop/ans3/ans.py
Normal file
126
bin/tests/system/resend_loop/ans3/ans.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# 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.edns
|
||||
import dns.name
|
||||
import dns.rcode
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from isctest.asyncserver import (
|
||||
AsyncDnsServer,
|
||||
DnsResponseSend,
|
||||
QueryContext,
|
||||
ResponseHandler,
|
||||
)
|
||||
|
||||
|
||||
def _get_cookie(qctx: QueryContext):
|
||||
for o in qctx.query.options:
|
||||
if o.otype == dns.edns.OptionType.COOKIE:
|
||||
cookie = o
|
||||
try:
|
||||
if len(cookie.server) == 0:
|
||||
cookie.server = b"\x11\x22\x33\x44\x55\x66\x77\x88"
|
||||
except AttributeError: # dnspython<2.7.0 compat
|
||||
if len(o.data) == 8:
|
||||
cookie.data *= 2
|
||||
|
||||
return cookie
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class PrimeHandler(ResponseHandler):
|
||||
"""
|
||||
Specifically handle priming query for "." NS (type 2)
|
||||
"""
|
||||
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
return len(qctx.qname.labels) == 0 and qctx.qtype == dns.rdatatype.NS
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
|
||||
ns_rrset = dns.rrset.from_text(
|
||||
".", dns.rdatatype.NS, qctx.qclass, "a.root-servers.nil."
|
||||
)
|
||||
a_rrset = dns.rrset.from_text(
|
||||
"a.root-servers.nil.", dns.rdatatype.A, qctx.qclass, "10.53.0.3"
|
||||
)
|
||||
|
||||
response = qctx.prepare_new_response(with_zone_data=False)
|
||||
response.set_rcode(dns.rcode.NOERROR)
|
||||
response.answer.append(ns_rrset)
|
||||
response.additional.append(a_rrset)
|
||||
|
||||
yield DnsResponseSend(response, authoritative=True)
|
||||
|
||||
|
||||
class CookieHandler(ResponseHandler):
|
||||
def match(self, qctx: QueryContext) -> bool:
|
||||
example = dns.name.from_text("example")
|
||||
return qctx.qname.is_subdomain(example)
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
|
||||
qctx.prepare_new_response()
|
||||
|
||||
# Check for client cookie
|
||||
cookie = _get_cookie(qctx)
|
||||
|
||||
# If missing cookie entirely, just return SERVFAIL
|
||||
if cookie is None:
|
||||
qctx.response.set_rcode(dns.rcode.SERVFAIL)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
# If there is a client cookie, mock BADCOOKIE to trigger
|
||||
# the resend loop logic.
|
||||
qctx.response.use_edns(options=[cookie])
|
||||
qctx.response.set_rcode(dns.rcode.BADCOOKIE)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
class NoErrorHandler(ResponseHandler):
|
||||
"""
|
||||
If the query is NOT a subdomain of example, respond with standard NOERROR empty answer
|
||||
"""
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
|
||||
qctx.prepare_new_response()
|
||||
qctx.response.set_rcode(dns.rcode.NOERROR)
|
||||
yield DnsResponseSend(qctx.response, authoritative=True)
|
||||
|
||||
|
||||
def resend_server() -> AsyncDnsServer:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
PrimeHandler(),
|
||||
CookieHandler(),
|
||||
NoErrorHandler(),
|
||||
)
|
||||
return server
|
||||
|
||||
|
||||
def main() -> None:
|
||||
resend_server().run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
16
bin/tests/system/resend_loop/ns4/named.conf.j2
Normal file
16
bin/tests/system/resend_loop/ns4/named.conf.j2
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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;
|
||||
dnssec-validation no;
|
||||
};
|
||||
|
||||
zone "." IN {
|
||||
type hint;
|
||||
file "root.hint";
|
||||
};
|
||||
14
bin/tests/system/resend_loop/ns4/root.hint
Normal file
14
bin/tests/system/resend_loop/ns4/root.hint
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
; 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
|
||||
. IN NS a.root-servers.nil.
|
||||
a.root-servers.nil. IN A 10.53.0.3
|
||||
28
bin/tests/system/resend_loop/tests_resend_loop.py
Normal file
28
bin/tests/system/resend_loop/tests_resend_loop.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
import dns.message
|
||||
|
||||
import isctest
|
||||
|
||||
|
||||
def test_resend_loop_badcookie(ns4):
|
||||
expected_log = "exceeded max queries resolving 'test.example/A'"
|
||||
|
||||
msg = dns.message.make_query("test.example", "A")
|
||||
with ns4.watch_log_from_here() as watcher:
|
||||
res = isctest.query.udp(msg, ns4.ip)
|
||||
watcher.wait_for_line(expected_log)
|
||||
|
||||
isctest.check.servfail(res)
|
||||
|
||||
prohibited_log = "query failed (timed out) for test.example/IN/A"
|
||||
assert prohibited_log not in ns4.log
|
||||
|
|
@ -979,10 +979,12 @@ if [ $ret != 0 ]; then echo_i "failed"; fi
|
|||
status=$((status + ret))
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "checking NXDOMAIN is returned when querying non existing domain in CH class ($n)"
|
||||
echo_i "checking REFUSED is returned when querying non existing domain in CH class ($n)"
|
||||
ret=0
|
||||
dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.${n} || ret=1
|
||||
grep "status: NXDOMAIN" dig.ns1.out.${n} >/dev/null || ret=1
|
||||
dig_with_opts @10.53.0.1 hostname.chaostest txt ch >dig.ns1.out.1.${n} || ret=1
|
||||
grep "status: NOERROR" dig.ns1.out.1.${n} >/dev/null || ret=1
|
||||
dig_with_opts @10.53.0.1 id.hostname txt ch >dig.ns1.out.2.${n} || ret=1
|
||||
grep "status: REFUSED" dig.ns1.out.2.${n} >/dev/null || ret=1
|
||||
if [ $ret != 0 ]; then echo_i "failed"; fi
|
||||
status=$((status + ret))
|
||||
|
||||
|
|
|
|||
28
bin/tests/system/selfpointedglue/ns1/named.conf.j2
Normal file
28
bin/tests/system/selfpointedglue/ns1/named.conf.j2
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See the COPYRIGHT file distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*/
|
||||
|
||||
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; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
};
|
||||
|
||||
zone "." {
|
||||
type primary;
|
||||
file "root.db";
|
||||
};
|
||||
24
bin/tests/system/selfpointedglue/ns1/root.db
Normal file
24
bin/tests/system/selfpointedglue/ns1/root.db
Normal 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 owner.root-servers.nil. a.root.servers.nil. (
|
||||
2010 ; serial
|
||||
600 ; refresh
|
||||
600 ; retry
|
||||
1200 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
. NS a.root-servers.nil.
|
||||
a.root-servers.nil. A 10.53.0.1
|
||||
|
||||
tld. NS ns.tld.
|
||||
ns.tld. A 10.53.0.2
|
||||
28
bin/tests/system/selfpointedglue/ns2/named.conf.j2
Normal file
28
bin/tests/system/selfpointedglue/ns2/named.conf.j2
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
*
|
||||
* SPDX-License-Identifier: MPL-2.0
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* See the COPYRIGHT file distributed with this work for additional
|
||||
* information regarding copyright ownership.
|
||||
*/
|
||||
|
||||
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; };
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
};
|
||||
|
||||
zone "tld." {
|
||||
type primary;
|
||||
file "tld.db";
|
||||
};
|
||||
27
bin/tests/system/selfpointedglue/ns2/tld.db
Normal file
27
bin/tests/system/selfpointedglue/ns2/tld.db
Normal 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
|
||||
tld. IN SOA owner.tld. ns.tld. (
|
||||
2010 ; serial
|
||||
600 ; refresh
|
||||
600 ; retry
|
||||
1200 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
tld. NS ns.tld.
|
||||
ns.tld. A 10.53.0.2
|
||||
|
||||
example.tld. NS ns.example.tld.
|
||||
ns.example.tld. A 10.53.0.3
|
||||
|
||||
example2.tld. NS ns.example2.tld.
|
||||
ns.example2.tld. A 10.53.0.3
|
||||
155
bin/tests/system/selfpointedglue/ns3/example.tld.db
Normal file
155
bin/tests/system/selfpointedglue/ns3/example.tld.db
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
; 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
|
||||
example.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. (
|
||||
2010 ; serial
|
||||
600 ; refresh
|
||||
600 ; retry
|
||||
1200 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
|
||||
example.tld. NS ns.example.tld.
|
||||
ns.example.tld. A 10.53.0.3
|
||||
|
||||
sub.example.tld. NS ns01.sub.example.tld.
|
||||
sub.example.tld. NS ns02.sub.example.tld.
|
||||
sub.example.tld. NS ns03.sub.example.tld.
|
||||
sub.example.tld. NS ns04.sub.example.tld.
|
||||
sub.example.tld. NS ns05.sub.example.tld.
|
||||
sub.example.tld. NS ns06.sub.example.tld.
|
||||
sub.example.tld. NS ns07.sub.example.tld.
|
||||
sub.example.tld. NS ns08.sub.example.tld.
|
||||
sub.example.tld. NS ns09.sub.example.tld.
|
||||
sub.example.tld. NS ns10.sub.example.tld.
|
||||
|
||||
ns01.sub.example.tld. A 10.53.0.5
|
||||
ns01.sub.example.tld. A 10.53.0.6
|
||||
ns01.sub.example.tld. A 10.53.0.7
|
||||
ns01.sub.example.tld. A 10.53.0.8
|
||||
ns01.sub.example.tld. A 10.53.0.9
|
||||
ns01.sub.example.tld. A 10.53.0.10
|
||||
ns01.sub.example.tld. A 10.53.1.1
|
||||
ns01.sub.example.tld. A 10.53.1.2
|
||||
ns01.sub.example.tld. A 10.53.2.1
|
||||
ns01.sub.example.tld. A 10.53.0.3
|
||||
; Those RR (same below) pointing to 127.0.0.1 won't ever be used as they
|
||||
; exceeded the ADB limit.
|
||||
ns01.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns02.sub.example.tld. A 10.53.0.5
|
||||
ns02.sub.example.tld. A 10.53.0.6
|
||||
ns02.sub.example.tld. A 10.53.0.7
|
||||
ns02.sub.example.tld. A 10.53.0.8
|
||||
ns02.sub.example.tld. A 10.53.0.9
|
||||
ns02.sub.example.tld. A 10.53.0.10
|
||||
ns02.sub.example.tld. A 10.53.1.1
|
||||
ns02.sub.example.tld. A 10.53.1.2
|
||||
ns02.sub.example.tld. A 10.53.2.1
|
||||
ns02.sub.example.tld. A 10.53.0.3
|
||||
ns02.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns03.sub.example.tld. A 10.53.0.5
|
||||
ns03.sub.example.tld. A 10.53.0.6
|
||||
ns03.sub.example.tld. A 10.53.0.7
|
||||
ns03.sub.example.tld. A 10.53.0.8
|
||||
ns03.sub.example.tld. A 10.53.0.9
|
||||
ns03.sub.example.tld. A 10.53.0.10
|
||||
ns03.sub.example.tld. A 10.53.1.1
|
||||
ns03.sub.example.tld. A 10.53.1.2
|
||||
ns03.sub.example.tld. A 10.53.2.1
|
||||
ns03.sub.example.tld. A 10.53.0.3
|
||||
ns03.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns04.sub.example.tld. A 10.53.0.5
|
||||
ns04.sub.example.tld. A 10.53.0.6
|
||||
ns04.sub.example.tld. A 10.53.0.7
|
||||
ns04.sub.example.tld. A 10.53.0.8
|
||||
ns04.sub.example.tld. A 10.53.0.9
|
||||
ns04.sub.example.tld. A 10.53.0.10
|
||||
ns04.sub.example.tld. A 10.53.1.1
|
||||
ns04.sub.example.tld. A 10.53.1.2
|
||||
ns04.sub.example.tld. A 10.53.2.1
|
||||
ns04.sub.example.tld. A 10.53.0.3
|
||||
ns04.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns05.sub.example.tld. A 10.53.0.5
|
||||
ns05.sub.example.tld. A 10.53.0.6
|
||||
ns05.sub.example.tld. A 10.53.0.7
|
||||
ns05.sub.example.tld. A 10.53.0.8
|
||||
ns05.sub.example.tld. A 10.53.0.9
|
||||
ns05.sub.example.tld. A 10.53.0.10
|
||||
ns05.sub.example.tld. A 10.53.1.1
|
||||
ns05.sub.example.tld. A 10.53.1.2
|
||||
ns05.sub.example.tld. A 10.53.2.1
|
||||
ns05.sub.example.tld. A 10.53.0.3
|
||||
ns05.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns06.sub.example.tld. A 10.53.0.5
|
||||
ns06.sub.example.tld. A 10.53.0.6
|
||||
ns06.sub.example.tld. A 10.53.0.7
|
||||
ns06.sub.example.tld. A 10.53.0.8
|
||||
ns06.sub.example.tld. A 10.53.0.9
|
||||
ns06.sub.example.tld. A 10.53.0.10
|
||||
ns06.sub.example.tld. A 10.53.1.1
|
||||
ns06.sub.example.tld. A 10.53.1.2
|
||||
ns06.sub.example.tld. A 10.53.2.1
|
||||
ns06.sub.example.tld. A 10.53.0.3
|
||||
ns06.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns07.sub.example.tld. A 10.53.0.5
|
||||
ns07.sub.example.tld. A 10.53.0.6
|
||||
ns07.sub.example.tld. A 10.53.0.7
|
||||
ns07.sub.example.tld. A 10.53.0.8
|
||||
ns07.sub.example.tld. A 10.53.0.9
|
||||
ns07.sub.example.tld. A 10.53.0.10
|
||||
ns07.sub.example.tld. A 10.53.1.1
|
||||
ns07.sub.example.tld. A 10.53.1.2
|
||||
ns07.sub.example.tld. A 10.53.2.1
|
||||
ns07.sub.example.tld. A 10.53.0.3
|
||||
ns07.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns08.sub.example.tld. A 10.53.0.5
|
||||
ns08.sub.example.tld. A 10.53.0.6
|
||||
ns08.sub.example.tld. A 10.53.0.7
|
||||
ns08.sub.example.tld. A 10.53.0.8
|
||||
ns08.sub.example.tld. A 10.53.0.9
|
||||
ns08.sub.example.tld. A 10.53.0.10
|
||||
ns08.sub.example.tld. A 10.53.1.1
|
||||
ns08.sub.example.tld. A 10.53.1.2
|
||||
ns08.sub.example.tld. A 10.53.2.1
|
||||
ns08.sub.example.tld. A 10.53.0.3
|
||||
ns08.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns09.sub.example.tld. A 10.53.0.5
|
||||
ns09.sub.example.tld. A 10.53.0.6
|
||||
ns09.sub.example.tld. A 10.53.0.7
|
||||
ns09.sub.example.tld. A 10.53.0.8
|
||||
ns09.sub.example.tld. A 10.53.0.9
|
||||
ns09.sub.example.tld. A 10.53.0.10
|
||||
ns09.sub.example.tld. A 10.53.1.1
|
||||
ns09.sub.example.tld. A 10.53.1.2
|
||||
ns09.sub.example.tld. A 10.53.2.1
|
||||
ns09.sub.example.tld. A 10.53.0.3
|
||||
ns09.sub.example.tld. A 127.0.0.1
|
||||
|
||||
ns10.sub.example.tld. A 10.53.0.5
|
||||
ns10.sub.example.tld. A 10.53.0.6
|
||||
ns10.sub.example.tld. A 10.53.0.7
|
||||
ns10.sub.example.tld. A 10.53.0.8
|
||||
ns10.sub.example.tld. A 10.53.0.9
|
||||
ns10.sub.example.tld. A 10.53.0.10
|
||||
ns10.sub.example.tld. A 10.53.1.1
|
||||
ns10.sub.example.tld. A 10.53.1.2
|
||||
ns10.sub.example.tld. A 10.53.2.1
|
||||
ns10.sub.example.tld. A 10.53.0.3
|
||||
ns10.sub.example.tld. A 127.0.0.1
|
||||
33
bin/tests/system/selfpointedglue/ns3/example2.tld.db
Normal file
33
bin/tests/system/selfpointedglue/ns3/example2.tld.db
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
; 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
|
||||
example2.tld. IN SOA owner.dnshoster.tld. ns.dnshoster.tld. (
|
||||
2010 ; serial
|
||||
600 ; refresh
|
||||
600 ; retry
|
||||
1200 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
|
||||
example2.tld. NS ns.example2.tld.
|
||||
ns.example2.tld. A 10.53.0.3
|
||||
|
||||
sub.example2.tld. NS ns01.sub.example2.tld.
|
||||
sub.example2.tld. NS ns02.sub.example2.tld.
|
||||
sub.example2.tld. NS ns03.sub.example2.tld.
|
||||
|
||||
ns01.sub.example2.tld. A 10.53.1.1
|
||||
ns01.sub.example2.tld. A 10.53.0.5
|
||||
ns02.sub.example2.tld. A 10.53.1.2
|
||||
ns02.sub.example2.tld. A 10.53.0.6
|
||||
ns03.sub.example2.tld. A 10.53.2.1
|
||||
ns03.sub.example2.tld. A 10.53.0.7
|
||||
44
bin/tests/system/selfpointedglue/ns3/named.conf.j2
Normal file
44
bin/tests/system/selfpointedglue/ns3/named.conf.j2
Normal 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.
|
||||
*/
|
||||
|
||||
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;
|
||||
10.53.0.5;
|
||||
10.53.0.6;
|
||||
10.53.0.7;
|
||||
10.53.0.8;
|
||||
10.53.0.9;
|
||||
10.53.0.10;
|
||||
10.53.1.1;
|
||||
10.53.1.2;
|
||||
10.53.2.1;
|
||||
};
|
||||
recursion no;
|
||||
dnssec-validation no;
|
||||
};
|
||||
|
||||
zone "example.tld." {
|
||||
type primary;
|
||||
file "example.tld.db";
|
||||
};
|
||||
|
||||
zone "example2.tld." {
|
||||
type primary;
|
||||
file "example2.tld.db";
|
||||
};
|
||||
3
bin/tests/system/selfpointedglue/ns4/named.args.j2
Normal file
3
bin/tests/system/selfpointedglue/ns4/named.args.j2
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{% set adblimit = adblimit | default("") %}
|
||||
|
||||
-D selfpointedglue-ns4 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4 @adblimit@
|
||||
59
bin/tests/system/selfpointedglue/ns4/named.conf.j2
Normal file
59
bin/tests/system/selfpointedglue/ns4/named.conf.j2
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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 maxdelegationservers = maxdelegationservers | default(None) %}
|
||||
|
||||
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; };
|
||||
recursion yes;
|
||||
dnssec-validation no;
|
||||
dnstap { resolver query; };
|
||||
dnstap-output file "dnstap.out";
|
||||
{% if maxdelegationservers %}
|
||||
@maxdelegationservers@
|
||||
{% endif %}
|
||||
};
|
||||
|
||||
/*
|
||||
* Forcing TCP ensures that ADDITIONAL won't be truncated (responses won't have
|
||||
* the TC flag, hence the resolver won't retry using TCP by itself, see
|
||||
* https://datatracker.ietf.org/doc/html/rfc2181#section-9)
|
||||
*/
|
||||
server 10.53.0.3 { tcp-only true; };
|
||||
server 10.53.0.5 { tcp-only true; };
|
||||
server 10.53.0.6 { tcp-only true; };
|
||||
server 10.53.0.7 { tcp-only true; };
|
||||
server 10.53.0.8 { tcp-only true; };
|
||||
server 10.53.0.9 { tcp-only true; };
|
||||
server 10.53.0.10 { tcp-only true; };
|
||||
server 10.53.1.1 { tcp-only true; };
|
||||
server 10.53.1.2 { tcp-only true; };
|
||||
server 10.53.2.1 { tcp-only true; };
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "root.hint";
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
14
bin/tests/system/selfpointedglue/ns4/root.hint
Normal file
14
bin/tests/system/selfpointedglue/ns4/root.hint
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
; 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
|
||||
. IN NS a.root-servers.nil.
|
||||
a.root-servers.nil. IN A 10.53.0.1
|
||||
20
bin/tests/system/selfpointedglue/prereq.sh
Normal file
20
bin/tests/system/selfpointedglue/prereq.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
$FEATURETEST --enable-dnstap || {
|
||||
echo_i "This test requires dnstap support." >&2
|
||||
exit 255
|
||||
}
|
||||
exit 0
|
||||
75
bin/tests/system/selfpointedglue/tests_selfpointedglue.py
Normal file
75
bin/tests/system/selfpointedglue/tests_selfpointedglue.py
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# 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
|
||||
|
||||
|
||||
def line_to_ips_and_queries(line):
|
||||
# dnstap-read output line example
|
||||
# 05-Feb-2026 11:00:57.853 RQ 10.53.0.4:38507 -> 10.53.0.3:22047 TCP 56b sub.example.tld/IN/NS
|
||||
_, _, _, _, _, dst, _, _, query = line.split(" ", 9)
|
||||
ip, _ = dst.split(":", 1)
|
||||
return (ip, query)
|
||||
|
||||
|
||||
def extract_dnstap(ns, nsid, expectedlen):
|
||||
ns.rndc("dnstap -roll 1")
|
||||
path = os.path.join(nsid, "dnstap.out.0")
|
||||
dnstapread = isctest.run.cmd(
|
||||
[os.getenv("DNSTAPREAD"), path],
|
||||
)
|
||||
|
||||
lines = dnstapread.out.splitlines()
|
||||
assert expectedlen == len(lines)
|
||||
return list(map(line_to_ips_and_queries, lines))
|
||||
|
||||
|
||||
# Because DNSTAP doesn't have ordering guarantee, the order doesn't matter here.
|
||||
def expect_ip_and_query(expected_ips_and_queries, ips_and_queries):
|
||||
found_count = 0
|
||||
for expected_ip, expected_query in expected_ips_and_queries:
|
||||
found = False
|
||||
for ip, query in ips_and_queries:
|
||||
if ip == expected_ip and query == expected_query:
|
||||
found = True
|
||||
found_count += 1
|
||||
break
|
||||
assert found
|
||||
assert found_count == len(expected_ips_and_queries)
|
||||
|
||||
|
||||
def test_selfpointedglue(ns4):
|
||||
msg = isctest.query.create("a.sub.example.tld.", "A")
|
||||
res = isctest.query.tcp(msg, ns4.ip)
|
||||
isctest.check.servfail(res)
|
||||
|
||||
ips_and_queries = extract_dnstap(ns4, "ns4", 10)
|
||||
|
||||
# Thanks to the de-duplication, only the first 6 NS IPs are
|
||||
# queried (once sub.example.tld. NS is found) instead of 60
|
||||
# (60 per NS, with 10 NS).
|
||||
expect_ip_and_query(
|
||||
[
|
||||
("10.53.0.1", "./IN/NS"),
|
||||
("10.53.0.1", "tld/IN/NS"),
|
||||
("10.53.0.2", "example.tld/IN/NS"),
|
||||
("10.53.0.3", "sub.example.tld/IN/NS"),
|
||||
("10.53.0.3", "a.sub.example.tld/IN/A"),
|
||||
("10.53.0.5", "a.sub.example.tld/IN/A"),
|
||||
("10.53.0.6", "a.sub.example.tld/IN/A"),
|
||||
("10.53.0.7", "a.sub.example.tld/IN/A"),
|
||||
("10.53.0.8", "a.sub.example.tld/IN/A"),
|
||||
("10.53.0.9", "a.sub.example.tld/IN/A"),
|
||||
],
|
||||
ips_and_queries,
|
||||
)
|
||||
18
bin/tests/system/srtt/README
Normal file
18
bin/tests/system/srtt/README
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
|
||||
SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
See the COPYRIGHT file distributed with this work for additional
|
||||
information regarding copyright ownership.
|
||||
|
||||
ns1 is root
|
||||
|
||||
ans{2-5} simulates four NS servers making authority on the same domain
|
||||
`example.`. ans2 is the quickest to answer, followed by ans3, then ans4, with
|
||||
ans5 being the slowest.
|
||||
|
||||
ns6 is a resolver
|
||||
36
bin/tests/system/srtt/ans2/ans.py
Normal file
36
bin/tests/system/srtt/ans2/ans.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
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 dns.rcode
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
|
||||
|
||||
from srtt_ans import DelayedQnameRangeHandler
|
||||
|
||||
|
||||
class Foo1ToFoo99Handler(DelayedQnameRangeHandler):
|
||||
max_qname = 99
|
||||
delay = 0.0
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
Foo1ToFoo99Handler(),
|
||||
IgnoreAllQueries(),
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
bin/tests/system/srtt/ans3/ans.py
Normal file
36
bin/tests/system/srtt/ans3/ans.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
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 dns.rcode
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
|
||||
|
||||
from srtt_ans import DelayedQnameRangeHandler
|
||||
|
||||
|
||||
class Foo1ToFoo199Handler(DelayedQnameRangeHandler):
|
||||
max_qname = 199
|
||||
delay = 0.03
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
Foo1ToFoo199Handler(),
|
||||
IgnoreAllQueries(),
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
bin/tests/system/srtt/ans4/ans.py
Normal file
36
bin/tests/system/srtt/ans4/ans.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
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 dns.rcode
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
|
||||
|
||||
from srtt_ans import DelayedQnameRangeHandler
|
||||
|
||||
|
||||
class Foo1ToFoo299Handler(DelayedQnameRangeHandler):
|
||||
max_qname = 299
|
||||
delay = 0.08
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
Foo1ToFoo299Handler(),
|
||||
IgnoreAllQueries(),
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
36
bin/tests/system/srtt/ans5/ans.py
Normal file
36
bin/tests/system/srtt/ans5/ans.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
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 dns.rcode
|
||||
|
||||
from isctest.asyncserver import AsyncDnsServer, IgnoreAllQueries
|
||||
|
||||
from srtt_ans import DelayedQnameRangeHandler
|
||||
|
||||
|
||||
class Foo1ToFoo399Handler(DelayedQnameRangeHandler):
|
||||
max_qname = 399
|
||||
delay = 0.15
|
||||
|
||||
|
||||
def main() -> None:
|
||||
server = AsyncDnsServer(default_aa=True, default_rcode=dns.rcode.NOERROR)
|
||||
server.install_response_handlers(
|
||||
Foo1ToFoo399Handler(),
|
||||
IgnoreAllQueries(),
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
29
bin/tests/system/srtt/ns1/named.conf.j2
Normal file
29
bin/tests/system/srtt/ns1/named.conf.j2
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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;
|
||||
};
|
||||
|
||||
zone "." {
|
||||
type primary;
|
||||
file "root.db";
|
||||
};
|
||||
36
bin/tests/system/srtt/ns1/root.db
Normal file
36
bin/tests/system/srtt/ns1/root.db
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
; 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 owner.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
|
||||
|
||||
; The idea is that the resolver would do 2 ADB lookups, so there would be 2
|
||||
; find list, both with 2 IPs in it. ns1 (which is actually ans2 and ans5) would
|
||||
; have both the slowest and fastest addresses. ns2 (which is actually ans3 and
|
||||
; ans4) would have two addresses in the middle.
|
||||
|
||||
example. NS ns1.example.
|
||||
example. NS ns1.example.
|
||||
example. NS ns2.example.
|
||||
example. NS ns2.example.
|
||||
|
||||
ns1.example. A 10.53.0.2 ; delay is 0
|
||||
ns1.example. A 10.53.0.5 ; delay is 0.15
|
||||
ns2.example. A 10.53.0.4 ; delay is 0.08
|
||||
ns2.example. A 10.53.0.3 ; delay is 0.03
|
||||
1
bin/tests/system/srtt/ns6/named.args
Normal file
1
bin/tests/system/srtt/ns6/named.args
Normal file
|
|
@ -0,0 +1 @@
|
|||
-D srtt-ns6 -m record -c named.conf -d 99 -g -T maxcachesize=2097152 -4
|
||||
41
bin/tests/system/srtt/ns6/named.conf.j2
Normal file
41
bin/tests/system/srtt/ns6/named.conf.j2
Normal 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.6;
|
||||
notify-source 10.53.0.6;
|
||||
transfer-source 10.53.0.6;
|
||||
port @PORT@;
|
||||
pid-file "named.pid";
|
||||
listen-on { 10.53.0.6; };
|
||||
listen-on-v6 { none; };
|
||||
recursion yes;
|
||||
dnssec-validation no;
|
||||
dnstap { resolver query; };
|
||||
dnstap-output file "dnstap.out";
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.6 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
zone "." {
|
||||
type hint;
|
||||
file "../../_common/root.hint";
|
||||
};
|
||||
20
bin/tests/system/srtt/prereq.sh
Normal file
20
bin/tests/system/srtt/prereq.sh
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
$FEATURETEST --enable-dnstap || {
|
||||
echo_i "This test requires dnstap support." >&2
|
||||
exit 255
|
||||
}
|
||||
exit 0
|
||||
59
bin/tests/system/srtt/srtt_ans.py
Normal file
59
bin/tests/system/srtt/srtt_ans.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
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 abc
|
||||
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rrset
|
||||
|
||||
from isctest.asyncserver import DnsResponseSend, QnameQtypeHandler, QueryContext
|
||||
|
||||
|
||||
class DelayedQnameRangeHandler(QnameQtypeHandler):
|
||||
"""
|
||||
Respond to queries for QNAMEs "foo1.example." through "foo<N>.example."
|
||||
with QTYPE=A, where <N> must be defined by the subclass. Every response is
|
||||
delayed by a fixed amount of time, which must also be defined (in seconds)
|
||||
by the subclass.
|
||||
"""
|
||||
|
||||
@property
|
||||
def qnames(self) -> list[str]:
|
||||
return [f"foo{x}.example." for x in range(1, self.max_qname + 1)]
|
||||
|
||||
qtypes = [dns.rdatatype.A]
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def max_qname(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def delay(self) -> float:
|
||||
raise NotImplementedError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.__class__.__name__}(foo[1-{self.max_qname}].example/A)"
|
||||
|
||||
async def get_responses(
|
||||
self, qctx: QueryContext
|
||||
) -> AsyncGenerator[DnsResponseSend, None]:
|
||||
a_rrset = dns.rrset.from_text(
|
||||
qctx.qname, 300, dns.rdataclass.IN, dns.rdatatype.A, "10.53.9.9"
|
||||
)
|
||||
qctx.response.answer.append(a_rrset)
|
||||
yield DnsResponseSend(qctx.response, delay=self.delay)
|
||||
86
bin/tests/system/srtt/tests_srtt.py
Normal file
86
bin/tests/system/srtt/tests_srtt.py
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# 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
|
||||
|
||||
|
||||
def line_to_dst_ips(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
|
||||
_, _, _, _, _, dst, _, _, _ = line.split(" ", 9)
|
||||
ip, _ = dst.split(":", 1)
|
||||
return ip
|
||||
|
||||
|
||||
def extract_dnstap(ns, nsid):
|
||||
ns.rndc("dnstap -roll 1")
|
||||
path = os.path.join(nsid, "dnstap.out.0")
|
||||
dnstapread = isctest.run.cmd(
|
||||
[os.getenv("DNSTAPREAD"), path],
|
||||
)
|
||||
|
||||
lines = dnstapread.out.splitlines()
|
||||
return map(line_to_dst_ips, lines)
|
||||
|
||||
|
||||
def assert_used_auth(ns, nsid, authip):
|
||||
ips = extract_dnstap(ns, nsid)
|
||||
queries = 0
|
||||
matches = 0
|
||||
for ip in ips:
|
||||
queries += 1
|
||||
if ip == authip:
|
||||
matches += 1
|
||||
assert matches > 85
|
||||
assert queries <= 115
|
||||
|
||||
|
||||
def test_srtt(ns6):
|
||||
for i in range(1, 100):
|
||||
msg = isctest.query.create(f"foo{i}.example.", "A")
|
||||
res = isctest.query.udp(msg, ns6.ip)
|
||||
isctest.check.noerror(res)
|
||||
assert len(res.answer[0]) == 1
|
||||
res.answer[0].ttl = 300
|
||||
assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
|
||||
|
||||
assert_used_auth(ns6, "ns6", "10.53.0.2")
|
||||
|
||||
for i in range(100, 200):
|
||||
msg = isctest.query.create(f"foo{i}.example.", "A")
|
||||
res = isctest.query.udp(msg, ns6.ip)
|
||||
isctest.check.noerror(res)
|
||||
assert len(res.answer[0]) == 1
|
||||
res.answer[0].ttl = 300
|
||||
assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
|
||||
|
||||
assert_used_auth(ns6, "ns6", "10.53.0.3")
|
||||
|
||||
for i in range(200, 300):
|
||||
msg = isctest.query.create(f"foo{i}.example.", "A")
|
||||
res = isctest.query.udp(msg, ns6.ip)
|
||||
isctest.check.noerror(res)
|
||||
assert len(res.answer[0]) == 1
|
||||
res.answer[0].ttl = 300
|
||||
assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
|
||||
|
||||
assert_used_auth(ns6, "ns6", "10.53.0.4")
|
||||
|
||||
for i in range(300, 400):
|
||||
msg = isctest.query.create(f"foo{i}.example.", "A")
|
||||
res = isctest.query.udp(msg, ns6.ip)
|
||||
isctest.check.noerror(res)
|
||||
assert len(res.answer[0]) == 1
|
||||
res.answer[0].ttl = 300
|
||||
assert str(res.answer[0]) == f"foo{i}.example. 300 IN A 10.53.9.9"
|
||||
assert_used_auth(ns6, "ns6", "10.53.0.5")
|
||||
BIN
bin/tests/system/tkeyleak/ns1/dns.keytab
Normal file
BIN
bin/tests/system/tkeyleak/ns1/dns.keytab
Normal file
Binary file not shown.
21
bin/tests/system/tkeyleak/ns1/example.db.in
Normal file
21
bin/tests/system/tkeyleak/ns1/example.db.in
Normal 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 300
|
||||
@ IN SOA ns.example. admin.example. (
|
||||
1 ; serial
|
||||
3600 ; refresh
|
||||
900 ; retry
|
||||
604800 ; expire
|
||||
300 ; minimum
|
||||
)
|
||||
@ IN NS ns.example.
|
||||
ns IN A 10.53.0.1
|
||||
39
bin/tests/system/tkeyleak/ns1/named.conf.j2
Normal file
39
bin/tests/system/tkeyleak/ns1/named.conf.j2
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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;
|
||||
dnssec-validation no;
|
||||
tkey-gssapi-keytab "dns.keytab";
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
zone "example" {
|
||||
type primary;
|
||||
file "example.db";
|
||||
};
|
||||
21
bin/tests/system/tkeyleak/prereq.sh
Normal file
21
bin/tests/system/tkeyleak/prereq.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
. ../conf.sh
|
||||
|
||||
$FEATURETEST --gssapi || {
|
||||
echo_i "gssapi not supported - skipping tkeyleak test"
|
||||
exit 255
|
||||
}
|
||||
|
||||
exit 0
|
||||
17
bin/tests/system/tkeyleak/setup.sh
Normal file
17
bin/tests/system/tkeyleak/setup.sh
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
#
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
#
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
#
|
||||
# See the COPYRIGHT file distributed with this work for additional
|
||||
# information regarding copyright ownership.
|
||||
|
||||
# shellcheck source=conf.sh
|
||||
. ../conf.sh
|
||||
|
||||
cp ns1/example.db.in ns1/example.db
|
||||
145
bin/tests/system/tkeyleak/tests_tkeyleak.py
Normal file
145
bin/tests/system/tkeyleak/tests_tkeyleak.py
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
Regression test for GSS-API context leak via repeated TKEY queries.
|
||||
|
||||
An unauthenticated attacker could exhaust server memory by sending
|
||||
repeated TKEY queries with crafted SPNEGO NegTokenInit tokens.
|
||||
Each query triggers gss_accept_sec_context() which returns
|
||||
GSS_S_CONTINUE_NEEDED and allocates a GSS context. On the unfixed
|
||||
code path, the context handle in process_gsstkey() is never stored
|
||||
or freed, leaking ~520 bytes per query.
|
||||
|
||||
The fix rejects GSS_S_CONTINUE_NEEDED in dst_gssapi_acceptctx() and
|
||||
deletes the context immediately.
|
||||
|
||||
The key distinguishing signal in the TKEY response:
|
||||
- CONTINUE (vulnerable): error=0, output token present, no TSIG
|
||||
- BADKEY (fixed): error=17, no output token
|
||||
"""
|
||||
|
||||
import struct
|
||||
import time
|
||||
|
||||
import dns.name
|
||||
import dns.query
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.rdtypes.ANY.TKEY
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
|
||||
pytestmark = pytest.mark.extra_artifacts(
|
||||
[
|
||||
"*/*.db",
|
||||
]
|
||||
)
|
||||
|
||||
TKEY_NAME = dns.name.from_text("test.key.")
|
||||
GSSAPI_ALGORITHM = dns.name.from_text("gss-tsig.")
|
||||
TKEY_MODE_GSSAPI = 3
|
||||
|
||||
# OID 1.2.840.113554.1.2.2 (Kerberos 5)
|
||||
KRB5_OID = b"\x06\x09\x2a\x86\x48\x86\xf7\x12\x01\x02\x02"
|
||||
|
||||
# OID 1.3.6.1.5.5.2 (SPNEGO)
|
||||
SPNEGO_OID = b"\x06\x06\x2b\x06\x01\x05\x05\x02"
|
||||
|
||||
|
||||
def der_encode(tag, data):
|
||||
"""Encode data in ASN.1 DER TLV format."""
|
||||
length = len(data)
|
||||
if length < 128:
|
||||
return tag + bytes([length]) + data
|
||||
if length < 256:
|
||||
return tag + b"\x81" + bytes([length]) + data
|
||||
return tag + b"\x82" + struct.pack(">H", length) + data
|
||||
|
||||
|
||||
def spnego_negtokeninit():
|
||||
"""Build a SPNEGO NegTokenInit proposing krb5 without a mechToken.
|
||||
|
||||
This forces gss_accept_sec_context() to return GSS_S_CONTINUE_NEEDED
|
||||
because the acceptor recognizes the krb5 mechanism but has not
|
||||
received an actual AP-REQ token yet.
|
||||
"""
|
||||
# MechTypeList ::= SEQUENCE OF MechType
|
||||
mechtype_list = der_encode(b"\x30", KRB5_OID)
|
||||
# [0] mechTypes
|
||||
mechtypes = der_encode(b"\xa0", mechtype_list)
|
||||
# NegTokenInit ::= SEQUENCE { mechTypes, ... }
|
||||
negtokeninit = der_encode(b"\x30", mechtypes)
|
||||
# [0] CONSTRUCTED (wrapping NegTokenInit)
|
||||
wrapped = der_encode(b"\xa0", negtokeninit)
|
||||
# APPLICATION 0 CONSTRUCTED (SPNEGO OID + body)
|
||||
return der_encode(b"\x60", SPNEGO_OID + wrapped)
|
||||
|
||||
|
||||
def make_tkey_query(token):
|
||||
"""Build a TKEY query with a GSS-API token in the additional section."""
|
||||
now = int(time.time())
|
||||
tkey_rdata = dns.rdtypes.ANY.TKEY.TKEY(
|
||||
rdclass=dns.rdataclass.ANY,
|
||||
rdtype=dns.rdatatype.TKEY,
|
||||
algorithm=GSSAPI_ALGORITHM,
|
||||
inception=now,
|
||||
expiration=now + 86400,
|
||||
mode=TKEY_MODE_GSSAPI,
|
||||
error=0,
|
||||
key=token,
|
||||
other=b"",
|
||||
)
|
||||
|
||||
msg = isctest.query.create(TKEY_NAME, dns.rdatatype.TKEY, dns.rdataclass.ANY)
|
||||
rrset = msg.find_rrset(
|
||||
msg.additional,
|
||||
TKEY_NAME,
|
||||
dns.rdataclass.ANY,
|
||||
dns.rdatatype.TKEY,
|
||||
create=True,
|
||||
)
|
||||
rrset.add(tkey_rdata)
|
||||
return msg
|
||||
|
||||
|
||||
def test_tkey_gssapi_no_continuation(ns1):
|
||||
"""TKEY with a SPNEGO NegTokenInit must be rejected, not continued.
|
||||
|
||||
On unfixed code, gss_accept_sec_context() returns CONTINUE_NEEDED
|
||||
and the response has error=0 with an output token (the leaked path).
|
||||
On fixed code, CONTINUE_NEEDED is rejected and the response has
|
||||
error=BADKEY(17) with no output token.
|
||||
"""
|
||||
port = ns1.ports.dns
|
||||
ip = ns1.ip
|
||||
|
||||
msg = make_tkey_query(spnego_negtokeninit())
|
||||
res = dns.query.tcp(msg, ip, port=port, timeout=5)
|
||||
|
||||
assert res is not None
|
||||
|
||||
tkey = get_tkey_answer(res)
|
||||
assert tkey is not None, "server did not return a TKEY answer"
|
||||
assert (
|
||||
tkey.error != 0
|
||||
), "server returned error=0 (GSS_S_CONTINUE_NEEDED not rejected)"
|
||||
assert len(tkey.key) == 0, "server returned a continuation token"
|
||||
|
||||
|
||||
def get_tkey_answer(response):
|
||||
"""Extract TKEY rdata from a DNS response, or None."""
|
||||
for rrset in response.answer:
|
||||
if rrset.rdtype == dns.rdatatype.TKEY:
|
||||
for rdata in rrset:
|
||||
return rdata
|
||||
return None
|
||||
|
|
@ -25,6 +25,11 @@ dig_cmd() {
|
|||
"$DIG" $DIGOPTS "$@" | grep -v '^;'
|
||||
}
|
||||
|
||||
dig_full() {
|
||||
# shellcheck disable=SC2086
|
||||
"$DIG" $DIGOPTS "$@"
|
||||
}
|
||||
|
||||
n=$((n + 1))
|
||||
echo_i "querying for various representations of an IN A record ($n)"
|
||||
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
||||
|
|
@ -81,8 +86,8 @@ n=$((n + 1))
|
|||
echo_i "querying for various representations of a CLASS10 TYPE1 record ($n)"
|
||||
for i in 1 2; do
|
||||
ret=0
|
||||
dig_cmd +short @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
|
||||
echo '\# 4 0A000001' | diff - dig.out.$i.test$n || ret=1
|
||||
dig_full @10.53.0.1 a$i.example a class10 >dig.out.$i.test$n
|
||||
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
|
||||
if [ $ret != 0 ]; then
|
||||
echo_i "#$i failed"
|
||||
fi
|
||||
|
|
@ -93,8 +98,8 @@ n=$((n + 1))
|
|||
echo_i "querying for various representations of a CLASS10 TXT record ($n)"
|
||||
for i in 1 2 3 4; do
|
||||
ret=0
|
||||
dig_cmd +short @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
|
||||
echo '"hello"' | diff - dig.out.$i.test$n || ret=1
|
||||
dig_full @10.53.0.1 txt$i.example txt class10 >dig.out.$i.test$n
|
||||
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
|
||||
if [ $ret != 0 ]; then
|
||||
echo_i "#$i failed"
|
||||
fi
|
||||
|
|
@ -105,8 +110,8 @@ n=$((n + 1))
|
|||
echo_i "querying for various representations of a CLASS10 TYPE123 record ($n)"
|
||||
for i in 1 2; do
|
||||
ret=0
|
||||
dig_cmd +short @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
|
||||
echo '\# 1 00' | diff - dig.out.$i.test$n || ret=1
|
||||
dig_full @10.53.0.1 unk$i.example type123 class10 >dig.out.$i.test$n
|
||||
grep -q "NOTIMP" dig.out.$i.test$n || ret=1
|
||||
if [ $ret != 0 ]; then
|
||||
echo_i "#$i failed"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ options {
|
|||
recursion no;
|
||||
dnssec-validation no;
|
||||
notify yes;
|
||||
|
||||
transfers-out 3;
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
|
|
|
|||
46
bin/tests/system/xferquota/ns3/named.conf.j2
Normal file
46
bin/tests/system/xferquota/ns3/named.conf.j2
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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;
|
||||
dnssec-validation no;
|
||||
|
||||
transfers-out 1;
|
||||
allow-transfer { 10.53.0.2; };
|
||||
};
|
||||
|
||||
key rndc_key {
|
||||
secret "1234abcd8765";
|
||||
algorithm @DEFAULT_HMAC@;
|
||||
};
|
||||
|
||||
controls {
|
||||
inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
|
||||
};
|
||||
|
||||
zone "." {
|
||||
type primary;
|
||||
file "root.db";
|
||||
};
|
||||
|
||||
zone "quota." {
|
||||
type primary;
|
||||
file "quota.db";
|
||||
};
|
||||
22
bin/tests/system/xferquota/ns3/quota.db
Normal file
22
bin/tests/system/xferquota/ns3/quota.db
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
||||
;
|
||||
; SPDX-License-Identifier: MPL-2.0
|
||||
;
|
||||
; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
; file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
;
|
||||
; See the COPYRIGHT file distributed with this work for additional
|
||||
; information regarding copyright ownership.
|
||||
|
||||
$TTL 300
|
||||
@ IN SOA ns1.quota. hostmaster.quota. (
|
||||
1 ; serial
|
||||
3600 ; refresh
|
||||
1800 ; retry
|
||||
604800 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
IN NS ns1.quota.
|
||||
ns1 IN A 10.53.0.3
|
||||
www IN A 10.0.0.1
|
||||
21
bin/tests/system/xferquota/ns3/root.db
Normal file
21
bin/tests/system/xferquota/ns3/root.db
Normal 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 300
|
||||
. IN SOA ns.root. hostmaster.root. (
|
||||
1 ; serial
|
||||
3600 ; refresh
|
||||
1800 ; retry
|
||||
604800 ; expire
|
||||
600 ; minimum
|
||||
)
|
||||
. NS a.root-servers.nil.
|
||||
a.root-servers.nil. A 10.53.0.3
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
# information regarding copyright ownership.
|
||||
|
||||
import glob
|
||||
import multiprocessing
|
||||
import os
|
||||
import re
|
||||
from re import compile as Re
|
||||
|
|
@ -18,6 +19,8 @@ import signal
|
|||
import time
|
||||
|
||||
import dns.message
|
||||
import dns.query
|
||||
import dns.zone
|
||||
import pytest
|
||||
|
||||
import isctest
|
||||
|
|
@ -59,6 +62,9 @@ def test_xferquota(named_port, servers):
|
|||
matching_line_count += 1
|
||||
return matching_line_count == 300
|
||||
|
||||
# The primary has 'transfers-out 3;', while the secondary has
|
||||
# 'transfers-in 5; transfer-per-ns 5;'. This will allow all the zones
|
||||
# to be eventually transferred, hitting the quotas now and then.
|
||||
isctest.run.retry_with_timeout(check_line_count, timeout=360)
|
||||
|
||||
axfr_msg = isctest.query.create("zone000099.example.", "AXFR")
|
||||
|
|
@ -79,3 +85,39 @@ def test_xferquota(named_port, servers):
|
|||
with servers["ns2"].watch_log_from_start(timeout=30) as watcher:
|
||||
watcher.wait_for_line(pattern)
|
||||
query_and_compare(a_msg)
|
||||
|
||||
|
||||
def _flood_unauthorized_axfrs(port, duration):
|
||||
"""Child process: send unauthorized AXFR requests for `duration` seconds."""
|
||||
deadline = time.monotonic() + duration
|
||||
while time.monotonic() < deadline:
|
||||
try:
|
||||
msg = dns.message.make_query("quota.", "AXFR")
|
||||
dns.query.tcp(msg, "10.53.0.3", port=port, timeout=2, source="10.53.0.1")
|
||||
except Exception: # pylint: disable=broad-exception-caught
|
||||
pass
|
||||
|
||||
|
||||
def test_xfrquota_unauthorized_no_starve(named_port):
|
||||
"""Unauthorized AXFR clients must not consume XFR-out quota (GL #3859).
|
||||
|
||||
ns3 is configured with transfers-out 1 and allow-transfer { 10.53.0.2; }.
|
||||
We flood AXFR requests from unauthorized source processes (10.53.0.1) and
|
||||
verify that an authorized client (10.53.0.2) can still transfer.
|
||||
"""
|
||||
with multiprocessing.Pool(10) as pool:
|
||||
pool.starmap_async(_flood_unauthorized_axfrs, [(named_port, 5)] * 10)
|
||||
|
||||
# Give the flood a moment to saturate
|
||||
time.sleep(1)
|
||||
|
||||
# Try an authorized AXFR from 10.53.0.2 multiple times to increase
|
||||
# the chance of hitting the race window where quota is consumed.
|
||||
zone = dns.zone.Zone("quota.")
|
||||
dns.query.inbound_xfr(
|
||||
"10.53.0.3",
|
||||
zone,
|
||||
port=named_port,
|
||||
timeout=10,
|
||||
source="10.53.0.2",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ Changelog
|
|||
development. Regular users should refer to :ref:`Release Notes <relnotes>`
|
||||
for changes relevant to them.
|
||||
|
||||
.. include:: ../changelog/changelog-9.18.49.rst
|
||||
.. include:: ../changelog/changelog-9.18.48.rst
|
||||
.. include:: ../changelog/changelog-9.18.47.rst
|
||||
.. include:: ../changelog/changelog-9.18.46.rst
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ The list of known issues affecting the latest version in the 9.18 branch can be
|
|||
found at
|
||||
https://gitlab.isc.org/isc-projects/bind9/-/wikis/Known-Issues-in-BIND-9.18
|
||||
|
||||
.. include:: ../notes/notes-9.18.49.rst
|
||||
.. include:: ../notes/notes-9.18.48.rst
|
||||
.. include:: ../notes/notes-9.18.47.rst
|
||||
.. include:: ../notes/notes-9.18.46.rst
|
||||
|
|
|
|||
226
doc/changelog/changelog-9.18.49.rst
Normal file
226
doc/changelog/changelog-9.18.49.rst
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
.. 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.
|
||||
|
||||
BIND 9.18.49
|
||||
------------
|
||||
|
||||
Security Fixes
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
- Fix outgoing zone transfers' quota issue. ``694648e14b``
|
||||
|
||||
Unauthorized clients could consume outgoing zone transfers quota and
|
||||
block authorized zone transfer clients. This has been fixed.
|
||||
:gl:`#3589`
|
||||
|
||||
- [CVE-2026-3592] Limit resolver server list size. ``5abfbc2663``
|
||||
|
||||
When resolving a domain with many nameservers that share overlapping
|
||||
IP addresses (e.g., 10 NS records all pointing at the same set of
|
||||
addresses), BIND could previously waste time querying duplicate
|
||||
addresses and build up excessively large server lists. Deduplicate
|
||||
addresses in the resolver's server list so that each unique IP is only
|
||||
queried once per resolution attempt, regardless of how many NS records
|
||||
point to it and cap the number of addresses stored per nameserver name
|
||||
to 6 (combined A and AAAA), preventing memory and CPU overhead from
|
||||
domains with unusually large NS/glue sets. :gl:`#5641`
|
||||
|
||||
- [CVE-2026-3039] Fix GSS-API resource leak. ``03ce21cf30``
|
||||
|
||||
Fixed a memory leak where each GSS-API TKEY negotiation leaked a
|
||||
security context inside the GSS library. An unauthenticated attacker
|
||||
could exhaust server memory by sending repeated TKEY queries to a
|
||||
server with tkey-gssapi-keytab configured. The leaked memory was
|
||||
allocated by the GSS library, bypassing BIND's memory accounting.
|
||||
|
||||
Multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) is now
|
||||
rejected, as BIND never supported it correctly and Kerberos/SPNEGO
|
||||
completes in a single round.
|
||||
|
||||
Also implemented missing RFC 3645 requirement: the client now verifies
|
||||
that mutual authentication and integrity flags are granted by the
|
||||
GSS-API mechanism (Section 3.1.1). :gl:`#5752`
|
||||
|
||||
- [CVE-2026-5950] Avoid unbounded recursion loop. ``43d173797e``
|
||||
|
||||
A bug during bad server handling could cause the resolver to enter an
|
||||
infinite loop, continuously sending queries to an upstream server with
|
||||
no exit condition, until the resolver query timeout was hit. This has
|
||||
been fixed.
|
||||
|
||||
ISC would like to thank Billy Baraja (BielraX) for bringing this issue
|
||||
to our attention. :gl:`#5804`
|
||||
|
||||
- [CVE-2026-5946] Disable recursion, UPDATE, and NOTIFY for non-IN
|
||||
views. ``7ce6ce37b1``
|
||||
|
||||
Recursion, dynamic updates (UPDATE), and zone change notifications
|
||||
(NOTIFY) are now disabled for views with a class other than IN (such
|
||||
as CHAOS or HESIOD); authoritative service for non-IN zones (e.g.
|
||||
version.bind in class CHAOS) continues to work as before. Servers
|
||||
configured with recursion yes in a non-IN view will log a warning at
|
||||
startup, and named-checkconf flags the same condition. UPDATE and
|
||||
NOTIFY messages that specify the meta-classes ANY or NONE in the
|
||||
question section are now rejected with FORMERR.
|
||||
|
||||
This addresses a set of closely related security issues collectively
|
||||
identified as CVE-2026-5946. ISC would like to thank Mcsky23 for
|
||||
bringing these issues to our attention.
|
||||
|
||||
Feature Changes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
- Revert isdelegation() to return boolean value again. ``83e5e8c4d0``
|
||||
|
||||
:gl:`#5838` :gl:`!11803`
|
||||
|
||||
- Fix CPU spikes and slow queries when cache approaches memory limit.
|
||||
``874a19c71b``
|
||||
|
||||
When the cache grew close to the configured max-cache-size, every
|
||||
subsequent entry triggered all worker threads to run cache cleanup at
|
||||
once, causing CPU spikes and a drop in query throughput. Cleanup is
|
||||
now spread probabilistically across inserts as memory approaches the
|
||||
limit, so the work is distributed evenly instead of piling up at the
|
||||
threshold.
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
- Fix named crash when processing SIG records in dynamic updates.
|
||||
``df77c239ac``
|
||||
|
||||
Previously, :iscman:`named` could abort if a client sent a dynamic
|
||||
update containing a SIG record (the legacy signature type) to a zone
|
||||
configured with an update-policy. The function `dns_db_findrdataset`
|
||||
had an incorrect requirements prerequisite that prevented SIG records
|
||||
being looked up, which was triggered as part of processing an UPDATE
|
||||
request and could be triggered remotely by any client permitted to
|
||||
send updates. This has been fixed by ensuring that SIG records are
|
||||
handled consistently with RRSIG records during update processing.
|
||||
:gl:`#5818` :gl:`!11877`
|
||||
|
||||
- Fix zone verification of NSEC3 signed zones. ``3a2e16ae65``
|
||||
|
||||
Previously, when computing the compressed bitmap during verification
|
||||
of an NSEC3-signed zone, an undersized buffer was used that resulted
|
||||
in an out-of-bounds write if there were too many active windows in the
|
||||
bitmap. This impacted mirror zones which are NSEC3-signed,
|
||||
`dnssec-signzone` and `dnssec-verifyzone`. This has been fixed.
|
||||
:gl:`#5834` :gl:`!11834`
|
||||
|
||||
- Prevent a crash when using both dns64 and filter-aaaa. ``891d055efc``
|
||||
|
||||
An assertion failure could be triggered if both `dns64` and the
|
||||
`filter-aaaa` plugin were in use simultaneously. This happened if the
|
||||
plugin triggered a second recursion process, which then attempted to
|
||||
store DNS64 state information in a pointer that had already been set
|
||||
by the original recursion process. This has been fixed. :gl:`#5854`
|
||||
:gl:`!11968`
|
||||
|
||||
- Remove unnecessary dns_name_free call. ``46aa4fd08d``
|
||||
|
||||
When processing a catalog zone member's primaries definition and there
|
||||
is a TXT record containing an invalid name TSIG key name,
|
||||
dns_name_free was incorrectly called triggering an assertion. This has
|
||||
been fixed. :gl:`#5858` :gl:`!11849`
|
||||
|
||||
- Tidy up the cleanup path in check_signer() ``03af408476``
|
||||
|
||||
When check_signer() processed a DNSKEY whose public-key data could not
|
||||
be parsed, the early return on the parse error skipped the cleanup of
|
||||
the cloned signature rdataset. In every code path that currently
|
||||
reaches this function the cloned rdataset holds no resources, so no
|
||||
memory was actually leaked, but the cleanup is restructured so the
|
||||
parse and the iteration cannot diverge again. :gl:`#5869` :gl:`!11960`
|
||||
|
||||
- Prevent malicious DNSSEC zones from exhausting validator CPU.
|
||||
``784725ef85``
|
||||
|
||||
A DNSSEC-signed zone could publish a DNSKEY with an unusually large
|
||||
RSA public exponent and force any validator resolving names in that
|
||||
zone to spend disproportionate CPU verifying signatures. The
|
||||
validator now rejects such DNSKEYs, matching the limit already applied
|
||||
to keys read from files or HSMs. :gl:`#5881` :gl:`!11924`
|
||||
|
||||
- Fix inverted gethostname() check in rndc status. ``c874e39a23``
|
||||
|
||||
The replacement of named_os_gethostname() with raw gethostname()
|
||||
inverted the success check: the "localhost" fallback runs on success,
|
||||
and on failure the uninitialized hostname buffer is read by
|
||||
snprintf(), leaking stack memory via the rndc status reply.
|
||||
:gl:`#5889` :gl:`!11883`
|
||||
|
||||
- Fix rndc-confgen aborting on HMAC-SHA-384/512 keys above 512 bits.
|
||||
``739e79592d``
|
||||
|
||||
`rndc-confgen -A hmac-sha384` and `-A hmac-sha512` documented a `-b`
|
||||
range of 1..1024, but any value above 512 aborted on hardened builds
|
||||
instead of producing a key. The full advertised range now works.
|
||||
:gl:`#5903` :gl:`!11911`
|
||||
|
||||
- Prevent crafted queries from degrading RRL performance. ``e81855244d``
|
||||
|
||||
With response rate limiting enabled, an attacker sending queries from
|
||||
many spoofed source addresses could steer entries into the same slot
|
||||
of the internal rate-limit table and slow down query processing on the
|
||||
affected server. The table now uses a per-process keyed hash so the
|
||||
placement of entries cannot be predicted or influenced from the
|
||||
network. :gl:`#5906` :gl:`!11953`
|
||||
|
||||
- Fix swapped arguments in redirect2() single-label branch.
|
||||
``9a969bf1bc``
|
||||
|
||||
On a recursive resolver with nxdomain-redirect configured, an NXDOMAIN
|
||||
result for a query whose qname is the root could corrupt the view's
|
||||
nxdomain-redirect target, after which the redirect feature stopped
|
||||
working for every subsequent query in that view until named was
|
||||
restarted. :gl:`#5908` :gl:`!11914`
|
||||
|
||||
- Fix a bug in allow-query/allow-transfer catalog zone custom
|
||||
properties. ``9e5a52e6fa``
|
||||
|
||||
The :iscman:`named` process could terminate unexpectedly when
|
||||
processing a catalog zone with an invalid ``allow-query`` or
|
||||
``allow-transfer`` custom property (i.e. having a non-APL type)
|
||||
coexisting with the valid property. This has been fixed. :gl:`#5941`
|
||||
:gl:`!11976`
|
||||
|
||||
- Fix a memory leak issue in the catalog zones. ``0b5874d3e1``
|
||||
|
||||
The :iscman:`named` process could leak small amounts of memory when
|
||||
processing a catalog zone entry which had defined custom primary
|
||||
servers with TSIG keys using both the regular ``primaries`` custom
|
||||
property syntax and the legacy alternative syntax (``masters``) at the
|
||||
same time. This has been fixed. :gl:`#5943` :gl:`!11974`
|
||||
|
||||
- Fix suppressed missing-glue check in named-checkzone. ``598277fe03``
|
||||
|
||||
named-checkzone and named-checkconf -z silently skipped the
|
||||
missing-glue check for any NS name that had already triggered an
|
||||
extra-AAAA-glue warning, so zones missing required A glue could pass
|
||||
validation and be deployed with broken delegations. :gl:`!11906`
|
||||
|
||||
- Pass empty string instead of NULL to ns_client_dumpmessage()
|
||||
``d489d825dc``
|
||||
|
||||
Pass "" instead of NULL to ns_client_dumpmessage() to get the log
|
||||
message printed.
|
||||
|
||||
- Reject record sets too large to serve in DNS. ``ab3d96b3e3``
|
||||
|
||||
When BIND was asked to store a record set whose total size exceeds
|
||||
what fits in a DNS message, it would allocate memory and build the
|
||||
structure, then fail later at response time. Such oversized record
|
||||
sets are now rejected at the time of storage with an error, avoiding
|
||||
wasted work on data that can never be served. :gl:`!11965`
|
||||
|
||||
|
||||
192
doc/notes/notes-9.18.49.rst
Normal file
192
doc/notes/notes-9.18.49.rst
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
.. 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.
|
||||
|
||||
Notes for BIND 9.18.49
|
||||
----------------------
|
||||
|
||||
Security Fixes
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
- Limit resolver server list size. :cve:`2026-3592`
|
||||
|
||||
When resolving a domain with many nameservers that shared overlapping
|
||||
IP addresses (e.g., 10 NS records all pointing at the same set of
|
||||
addresses), BIND could previously waste time querying duplicate
|
||||
addresses and build up excessively large server lists. Addresses in
|
||||
the resolver's server list are now deduplicated so that each unique IP is only
|
||||
queried once per resolution attempt, regardless of how many NS records
|
||||
point to it. The number of addresses stored per nameserver name
|
||||
is also now capped at six (combined A and AAAA), preventing memory and CPU overhead from
|
||||
domains with unusually large NS/glue sets.
|
||||
|
||||
ISC would like to thank Shuhan Zhang from Tsinghua University for
|
||||
reporting this issue. :gl:`#5641`
|
||||
|
||||
- Fix GSS-API resource leak. :cve:`2026-3039`
|
||||
|
||||
A memory leak was fixed where each GSS-API TKEY negotiation leaked a
|
||||
security context inside the GSS library. An unauthenticated attacker
|
||||
could exhaust server memory by sending repeated TKEY queries to a
|
||||
server with :any:`tkey-gssapi-keytab` configured. The leaked memory was
|
||||
allocated by the GSS library, bypassing BIND's memory accounting.
|
||||
|
||||
Multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) is now
|
||||
rejected, as BIND never supported it correctly and Kerberos/SPNEGO
|
||||
completes in a single round.
|
||||
|
||||
ISC would like to thank Vitaly Simonovich for bringing this
|
||||
vulnerability to our attention. :gl:`#5752`
|
||||
|
||||
- Disable recursion, UPDATE, and NOTIFY for non-IN views.
|
||||
:cve:`2026-5946`
|
||||
|
||||
Recursion, dynamic updates (UPDATE), and zone change notifications
|
||||
(NOTIFY) are now disabled for views with a class other than IN (such
|
||||
as CHAOS or HESIOD); authoritative service for non-IN zones (e.g.
|
||||
version.bind in class CHAOS) continues to work as before. Servers
|
||||
configured with :namedconf:ref:`recursion yes; <recursion>`
|
||||
in a non-IN view log a warning at
|
||||
startup, and :iscman:`named-checkconf` flags the same condition. UPDATE and
|
||||
NOTIFY messages that specify the meta-classes ANY or NONE in the
|
||||
question section are now rejected with FORMERR.
|
||||
|
||||
This addresses a set of closely related security issues collectively
|
||||
identified as CVE-2026-5946. ISC would like to thank Mcsky23 for
|
||||
bringing these issues to our attention. :gl:`#5784`
|
||||
|
||||
- Avoid unbounded recursion loop. :cve:`2026-5950`
|
||||
|
||||
A bug during bad server handling could cause the resolver to enter an
|
||||
infinite loop, continuously sending queries to an upstream server with
|
||||
no exit condition, until the resolver query timeout was hit. This has
|
||||
been fixed.
|
||||
|
||||
ISC would like to thank Billy Baraja (BielraX) for bringing this issue
|
||||
to our attention. :gl:`#5804`
|
||||
|
||||
- Fix outgoing zone transfers' quota issue.
|
||||
|
||||
Unauthorized clients could consume the entire outgoing zone-transfer quota and
|
||||
block authorized zone transfer clients. This has been fixed.
|
||||
:gl:`#3589`
|
||||
|
||||
Feature Changes
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
- Fix CPU spikes and slow queries when cache approaches memory limit.
|
||||
|
||||
Cache cleanup is now spread probabilistically to avoid CPU usage spikes and a
|
||||
drop in query throughput. :gl:`#5891`
|
||||
|
||||
Bug Fixes
|
||||
~~~~~~~~~
|
||||
|
||||
- Fix :iscman:`named` crash when processing SIG records in dynamic updates.
|
||||
|
||||
Previously, :iscman:`named` could abort if a client sent a dynamic
|
||||
update containing a SIG record (the legacy signature type) to a zone
|
||||
configured with an update-policy. The function `dns_db_findrdataset`
|
||||
had an incorrect requirements prerequisite that prevented SIG records
|
||||
from being looked up, which was triggered as part of processing an UPDATE
|
||||
request and could be triggered remotely by any client permitted to
|
||||
send updates. This has been fixed by ensuring that SIG records are
|
||||
handled consistently with RRSIG records during update processing.
|
||||
:gl:`#5818`
|
||||
|
||||
- Fix :option:`rndc modzone` behavior for a zone in named.conf.
|
||||
|
||||
If a zone was present in the configuration file and not originally
|
||||
added by :option:`rndc addzone`, :option:`rndc modzone` for that zone would succeed
|
||||
once but subsequent :option:`rndc modzone` attempts would fail. This has been
|
||||
fixed. :gl:`#5826`
|
||||
|
||||
- Fix zone verification of NSEC3 signed zones.
|
||||
|
||||
Previously, when computing the compressed bitmap during verification
|
||||
of an NSEC3-signed zone, an undersized buffer was used that resulted
|
||||
in an out-of-bounds write if there were too many active windows in the
|
||||
bitmap. This impacted the mirror zones which are NSEC3-signed,
|
||||
:iscman:`dnssec-signzone` and :iscman:`dnssec-verify`. This has been fixed.
|
||||
:gl:`#5834`
|
||||
|
||||
- Prevent a crash when using both :any:`dns64` and :any:`filter-aaaa`.
|
||||
|
||||
An assertion failure could be triggered if both :any:`dns64` and the
|
||||
:any:`filter-aaaa` plugin were in use simultaneously. This happened if the
|
||||
plugin triggered a second recursion process, which then attempted to
|
||||
store DNS64 state information in a pointer that had already been set
|
||||
by the original recursion process. This has been fixed. :gl:`#5854`
|
||||
|
||||
- Fixed an assertion failure when processing catalog zones.
|
||||
|
||||
If a TXT record containing an invalid name TSIG key name was found
|
||||
when processing a catalog zone member's primaries definition,
|
||||
``dns_name_free`` was incorrectly called, triggering an assertion. This has
|
||||
been fixed. :gl:`#5858`
|
||||
|
||||
- Prevent malicious DNSSEC zones from exhausting validator CPU.
|
||||
|
||||
A DNSSEC-signed zone could publish a DNSKEY with an unusually large
|
||||
RSA public exponent and force any validator resolving names in that
|
||||
zone to spend disproportionate CPU verifying signatures. The
|
||||
validator now rejects such DNSKEYs, matching the limit already applied
|
||||
to keys read from files or HSMs. :gl:`#5881`
|
||||
|
||||
- Fix :iscman:`rndc-confgen` aborting on HMAC-SHA-384/512 keys above 512 bits.
|
||||
|
||||
:iscman:`rndc-confgen` (with either ``-A hmac-sha384`` or
|
||||
``-A hmac-sha512``) previously documented a ``-b``
|
||||
range of 1..1024, but any value above 512 aborted on hardened builds
|
||||
instead of producing a key. The full advertised range now works.
|
||||
:gl:`#5903`
|
||||
|
||||
- Prevent crafted queries from degrading RRL performance.
|
||||
|
||||
With response rate limiting enabled, an attacker sending queries from
|
||||
many spoofed source addresses could steer entries into the same slot
|
||||
of the internal rate-limit table and slow down query processing on the
|
||||
affected server. The table now uses a per-process keyed hash so the
|
||||
placement of entries cannot be predicted or influenced from the
|
||||
network. :gl:`#5906`
|
||||
|
||||
- Fix a bug in :any:`allow-query`/:any:`allow-transfer` catalog zone custom
|
||||
properties.
|
||||
|
||||
The :iscman:`named` process could terminate unexpectedly when
|
||||
processing a catalog zone with an invalid :any:`allow-query` or
|
||||
:any:`allow-transfer` custom property (i.e. having a non-APL type)
|
||||
coexisting with the valid property. This has been fixed. :gl:`#5941`
|
||||
|
||||
- Fix a memory leak issue in catalog zones.
|
||||
|
||||
The :iscman:`named` process could leak small amounts of memory when
|
||||
processing a catalog zone entry which had defined custom primary
|
||||
servers with TSIG keys, if both the regular ``primaries`` custom
|
||||
property syntax and the legacy alternative syntax (``masters``) were used at the
|
||||
same time. This has been fixed. :gl:`#5943`
|
||||
|
||||
- Fix suppressed missing-glue check in :iscman:`named-checkzone`.
|
||||
|
||||
:iscman:`named-checkzone` and :option:`named-checkconf -z` silently
|
||||
skipped the missing-glue check for any NS name that had already
|
||||
triggered an extra-AAAA-glue warning, so zones missing required A glue
|
||||
could pass validation and be deployed with broken delegations.
|
||||
:gl:`!11899`
|
||||
|
||||
- Reject record sets too large to serve in DNS.
|
||||
|
||||
When BIND was asked to store a record set whose total size exceeded
|
||||
what fit in a DNS message, it would allocate memory and build the
|
||||
structure, then fail later at response time. Such oversized record
|
||||
sets are now rejected at the time of storage with an error, avoiding
|
||||
wasted work on data that can never be served. :gl:`!11963`
|
||||
|
||||
|
||||
|
|
@ -2789,13 +2789,17 @@ check_mirror_zone_notify(const cfg_obj_t *zoptions, const char *znamestr,
|
|||
*/
|
||||
static bool
|
||||
check_recursion(const cfg_obj_t *config, const cfg_obj_t *voptions,
|
||||
const cfg_obj_t *goptions, isc_log_t *logctx,
|
||||
cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
|
||||
dns_rdataclass_t vclass, const cfg_obj_t *goptions,
|
||||
isc_log_t *logctx, cfg_aclconfctx_t *actx, isc_mem_t *mctx) {
|
||||
dns_acl_t *acl = NULL;
|
||||
const cfg_obj_t *obj;
|
||||
isc_result_t result;
|
||||
bool retval = true;
|
||||
|
||||
if (vclass != dns_rdataclass_in) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check the "recursion" option first.
|
||||
*/
|
||||
|
|
@ -3380,7 +3384,8 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions,
|
|||
* contradicts the purpose of the former.
|
||||
*/
|
||||
if (ztype == CFG_ZONE_MIRROR &&
|
||||
!check_recursion(config, voptions, goptions, logctx, actx, mctx))
|
||||
!check_recursion(config, voptions, zclass, goptions, logctx, actx,
|
||||
mctx))
|
||||
{
|
||||
cfg_obj_log(zoptions, logctx, ISC_LOG_ERROR,
|
||||
"zone '%s': mirror zones cannot be used if "
|
||||
|
|
@ -5215,6 +5220,17 @@ check_viewconf(const cfg_obj_t *config, const cfg_obj_t *voptions,
|
|||
|
||||
cfg_aclconfctx_create(mctx, &actx);
|
||||
|
||||
if (vclass != dns_rdataclass_in) {
|
||||
if (check_recursion(config, voptions, dns_rdataclass_in,
|
||||
options, logctx, actx, mctx))
|
||||
{
|
||||
cfg_obj_log(opts, logctx, ISC_LOG_WARNING,
|
||||
"recursion will be disabled for "
|
||||
"non-IN view '%s'",
|
||||
viewname);
|
||||
}
|
||||
}
|
||||
|
||||
if (voptions != NULL) {
|
||||
(void)cfg_map_get(voptions, "zone", &zones);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -86,6 +86,15 @@
|
|||
|
||||
#define DNS_ADB_MINADBSIZE (1024U * 1024U) /*%< 1 Megabyte */
|
||||
|
||||
/*
|
||||
* Default and override for the per-find address limit, the sum of the number of
|
||||
* A and AAAA RR from an ADB NS name resolution. When non-zero, this value is
|
||||
* used instead of the default. Can be set via 'named -T adbaddrslimit=N' for
|
||||
* testing.
|
||||
*/
|
||||
#define DEFAULT_ADDRSLIMIT 6
|
||||
size_t dns_adb_addrslimit = 0;
|
||||
|
||||
typedef ISC_LIST(dns_adbname_t) dns_adbnamelist_t;
|
||||
typedef struct dns_adbnamehook dns_adbnamehook_t;
|
||||
typedef ISC_LIST(dns_adbnamehook_t) dns_adbnamehooklist_t;
|
||||
|
|
@ -940,7 +949,7 @@ import_rdataset(dns_adbname_t *adbname, dns_rdataset_t *rdataset,
|
|||
INSIST(DNS_ADB_VALID(adb));
|
||||
|
||||
rdtype = rdataset->type;
|
||||
INSIST((rdtype == dns_rdatatype_a) || (rdtype == dns_rdatatype_aaaa));
|
||||
REQUIRE(rdtype == dns_rdatatype_a || rdtype == dns_rdatatype_aaaa);
|
||||
|
||||
addr_bucket = DNS_ADB_INVALIDBUCKET;
|
||||
new_addresses_added = false;
|
||||
|
|
@ -2200,6 +2209,9 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
|
|||
dns_adbaddrinfo_t *addrinfo;
|
||||
dns_adbentry_t *entry;
|
||||
int bucket;
|
||||
size_t count = 0;
|
||||
size_t limit = dns_adb_addrslimit != 0 ? dns_adb_addrslimit
|
||||
: DEFAULT_ADDRSLIMIT;
|
||||
|
||||
bucket = DNS_ADB_INVALIDBUCKET;
|
||||
|
||||
|
|
@ -2232,6 +2244,13 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
|
|||
inc_entry_refcnt(adb, entry, false);
|
||||
ISC_LIST_APPEND(find->list, addrinfo, publink);
|
||||
addrinfo = NULL;
|
||||
|
||||
if (++count >= limit) {
|
||||
DP(ISC_LOG_DEBUG(3), "skipping addresses");
|
||||
UNLOCK(&adb->entrylocks[bucket]);
|
||||
return;
|
||||
}
|
||||
|
||||
nextv4:
|
||||
UNLOCK(&adb->entrylocks[bucket]);
|
||||
bucket = DNS_ADB_INVALIDBUCKET;
|
||||
|
|
@ -2267,6 +2286,13 @@ copy_namehook_lists(dns_adb_t *adb, dns_adbfind_t *find,
|
|||
inc_entry_refcnt(adb, entry, false);
|
||||
ISC_LIST_APPEND(find->list, addrinfo, publink);
|
||||
addrinfo = NULL;
|
||||
|
||||
if (++count >= limit) {
|
||||
DP(ISC_LOG_DEBUG(3), "skipping addresses");
|
||||
UNLOCK(&adb->entrylocks[bucket]);
|
||||
return;
|
||||
}
|
||||
|
||||
nextv6:
|
||||
UNLOCK(&adb->entrylocks[bucket]);
|
||||
bucket = DNS_ADB_INVALIDBUCKET;
|
||||
|
|
|
|||
|
|
@ -604,7 +604,14 @@ dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
|
|||
GSS_SPNEGO_MECHANISM, flags, 0, NULL, gintokenp, NULL,
|
||||
&gouttoken, &ret_flags, NULL);
|
||||
|
||||
if (gret != GSS_S_COMPLETE && gret != GSS_S_CONTINUE_NEEDED) {
|
||||
switch (gret) {
|
||||
case GSS_S_COMPLETE:
|
||||
result = ISC_R_SUCCESS;
|
||||
break;
|
||||
case GSS_S_CONTINUE_NEEDED:
|
||||
result = DNS_R_CONTINUE;
|
||||
break;
|
||||
default:
|
||||
gss_err_message(mctx, gret, minor, err_message);
|
||||
if (err_message != NULL && *err_message != NULL) {
|
||||
gss_log(3, "Failure initiating security context: %s",
|
||||
|
|
@ -629,12 +636,6 @@ dst_gssapi_initctx(const dns_name_t *name, isc_buffer_t *intoken,
|
|||
CHECK(isc_buffer_copyregion(outtoken, &r));
|
||||
}
|
||||
|
||||
if (gret == GSS_S_COMPLETE) {
|
||||
result = ISC_R_SUCCESS;
|
||||
} else {
|
||||
result = DNS_R_CONTINUE;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (gouttoken.length != 0U) {
|
||||
(void)gss_release_buffer(&minor, &gouttoken);
|
||||
|
|
@ -645,7 +646,7 @@ cleanup:
|
|||
|
||||
isc_result_t
|
||||
dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
||||
isc_region_t *intoken, isc_buffer_t **outtoken,
|
||||
isc_region_t *intoken, isc_buffer_t **outtokenp,
|
||||
dns_gss_ctx_id_t *ctxout, dns_name_t *principal,
|
||||
isc_mem_t *mctx) {
|
||||
isc_region_t r;
|
||||
|
|
@ -658,16 +659,11 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
|||
isc_result_t result;
|
||||
char buf[1024];
|
||||
|
||||
REQUIRE(outtoken != NULL && *outtoken == NULL);
|
||||
REQUIRE(outtokenp != NULL && *outtokenp == NULL);
|
||||
REQUIRE(*ctxout == NULL);
|
||||
|
||||
REGION_TO_GBUFFER(*intoken, gintoken);
|
||||
|
||||
if (*ctxout == NULL) {
|
||||
context = GSS_C_NO_CONTEXT;
|
||||
} else {
|
||||
context = *ctxout;
|
||||
}
|
||||
|
||||
if (gssapi_keytab != NULL) {
|
||||
#if HAVE_GSSAPI_GSSAPI_KRB5_H || HAVE_GSSAPI_KRB5_H
|
||||
gret = gsskrb5_register_acceptor_identity(gssapi_keytab);
|
||||
|
|
@ -712,8 +708,15 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
|||
|
||||
switch (gret) {
|
||||
case GSS_S_COMPLETE:
|
||||
case GSS_S_CONTINUE_NEEDED:
|
||||
break;
|
||||
/*
|
||||
* RFC 3645 4.1.3: we don't handle GSS_S_CONTINUE_NEEDED
|
||||
* Multi-round GSS-API negotiation is not supported.
|
||||
*/
|
||||
case GSS_S_CONTINUE_NEEDED:
|
||||
gss_log(3, "multi-round GSS-API negotiation not supported");
|
||||
(void)gss_delete_sec_context(&minor, &context, NULL);
|
||||
FALLTHROUGH;
|
||||
case GSS_S_DEFECTIVE_TOKEN:
|
||||
case GSS_S_DEFECTIVE_CREDENTIAL:
|
||||
case GSS_S_BAD_SIG:
|
||||
|
|
@ -726,7 +729,7 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
|||
case GSS_S_BAD_MECH:
|
||||
case GSS_S_FAILURE:
|
||||
result = DNS_R_INVALIDTKEY;
|
||||
/* fall through */
|
||||
FALLTHROUGH;
|
||||
default:
|
||||
gss_log(3, "failed gss_accept_sec_context: %s",
|
||||
gss_error_tostring(gret, minor, buf, sizeof(buf)));
|
||||
|
|
@ -737,50 +740,55 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
|||
}
|
||||
|
||||
if (gouttoken.length > 0U) {
|
||||
isc_buffer_allocate(mctx, outtoken,
|
||||
isc_buffer_allocate(mctx, outtokenp,
|
||||
(unsigned int)gouttoken.length);
|
||||
GBUFFER_TO_REGION(gouttoken, r);
|
||||
CHECK(isc_buffer_copyregion(*outtoken, &r));
|
||||
CHECK(isc_buffer_copyregion(*outtokenp, &r));
|
||||
(void)gss_release_buffer(&minor, &gouttoken);
|
||||
}
|
||||
|
||||
if (gret == GSS_S_COMPLETE) {
|
||||
gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
|
||||
if (gret != GSS_S_COMPLETE) {
|
||||
gss_log(3, "failed gss_display_name: %s",
|
||||
gss_error_tostring(gret, minor, buf,
|
||||
sizeof(buf)));
|
||||
CHECK(ISC_R_FAILURE);
|
||||
}
|
||||
INSIST(gret == GSS_S_COMPLETE);
|
||||
|
||||
/*
|
||||
* Compensate for a bug in Solaris8's implementation
|
||||
* of gss_display_name(). Should be harmless in any
|
||||
* case, since principal names really should not
|
||||
* contain null characters.
|
||||
*/
|
||||
if (gnamebuf.length > 0U &&
|
||||
((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
|
||||
{
|
||||
gnamebuf.length--;
|
||||
}
|
||||
|
||||
gss_log(3, "gss-api source name (accept) is %.*s",
|
||||
(int)gnamebuf.length, (char *)gnamebuf.value);
|
||||
|
||||
GBUFFER_TO_REGION(gnamebuf, r);
|
||||
isc_buffer_init(&namebuf, r.base, r.length);
|
||||
isc_buffer_add(&namebuf, r.length);
|
||||
|
||||
CHECK(dns_name_fromtext(principal, &namebuf, dns_rootname, 0,
|
||||
NULL));
|
||||
} else {
|
||||
result = DNS_R_CONTINUE;
|
||||
gret = gss_display_name(&minor, gname, &gnamebuf, NULL);
|
||||
if (gret != GSS_S_COMPLETE) {
|
||||
gss_log(3, "failed gss_display_name: %s",
|
||||
gss_error_tostring(gret, minor, buf, sizeof(buf)));
|
||||
result = ISC_R_FAILURE;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compensate for a bug in Solaris8's implementation
|
||||
* of gss_display_name(). Should be harmless in any
|
||||
* case, since principal names really should not
|
||||
* contain null characters.
|
||||
*/
|
||||
if (gnamebuf.length > 0U &&
|
||||
((char *)gnamebuf.value)[gnamebuf.length - 1] == '\0')
|
||||
{
|
||||
gnamebuf.length--;
|
||||
}
|
||||
|
||||
gss_log(3, "gss-api source name (accept) is %.*s", (int)gnamebuf.length,
|
||||
(char *)gnamebuf.value);
|
||||
|
||||
GBUFFER_TO_REGION(gnamebuf, r);
|
||||
isc_buffer_init(&namebuf, r.base, r.length);
|
||||
isc_buffer_add(&namebuf, r.length);
|
||||
|
||||
CHECK(dns_name_fromtext(principal, &namebuf, dns_rootname, 0, NULL));
|
||||
|
||||
*ctxout = context;
|
||||
|
||||
cleanup:
|
||||
if (result != ISC_R_SUCCESS && *outtokenp != NULL) {
|
||||
isc_buffer_free(outtokenp);
|
||||
}
|
||||
|
||||
if (result != ISC_R_SUCCESS && context != GSS_C_NO_CONTEXT) {
|
||||
(void)gss_delete_sec_context(&minor, &context, NULL);
|
||||
}
|
||||
|
||||
if (gnamebuf.length != 0U) {
|
||||
gret = gss_release_buffer(&minor, &gnamebuf);
|
||||
if (gret != GSS_S_COMPLETE) {
|
||||
|
|
|
|||
|
|
@ -113,20 +113,17 @@ dst_gssapi_acceptctx(dns_gss_cred_id_t cred, const char *gssapi_keytab,
|
|||
* generated by gss_accept_sec_context() to be sent to the
|
||||
* initiator
|
||||
* 'context' is a valid pointer to receive the generated context handle.
|
||||
* On the initial call, it should be a pointer to NULL, which
|
||||
* will be allocated as a dns_gss_ctx_id_t. Subsequent calls
|
||||
* should pass in the handle generated on the first call.
|
||||
* Call dst_gssapi_releasecred to delete the context and free
|
||||
* the memory.
|
||||
*
|
||||
* Requires:
|
||||
* 'outtoken' to != NULL && *outtoken == NULL.
|
||||
* 'outtoken' != NULL && *outtoken == NULL.
|
||||
* 'context' != NULL && *context == NULL.
|
||||
*
|
||||
* Returns:
|
||||
* ISC_R_SUCCESS msg was successfully updated to include the
|
||||
* query to be sent
|
||||
* DNS_R_CONTINUE transaction still in progress
|
||||
* other an error occurred while building the message
|
||||
* ISC_R_SUCCESS msg was successfully updated to include
|
||||
* the query to be sent
|
||||
* DNS_R_INVALIDTKEY an error occurred while accepting the
|
||||
* context
|
||||
* ISC_R_FAILURE other error occurred
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
|
|
|
|||
|
|
@ -1080,6 +1080,17 @@ getquestions(isc_buffer_t *source, dns_message_t *msg, dns_decompress_t *dctx,
|
|||
rdtype = isc_buffer_getuint16(source);
|
||||
rdclass = isc_buffer_getuint16(source);
|
||||
|
||||
/*
|
||||
* Notify and update messages need to specify the data class.
|
||||
*/
|
||||
if ((msg->opcode == dns_opcode_update ||
|
||||
msg->opcode == dns_opcode_notify) &&
|
||||
(rdclass == dns_rdataclass_none ||
|
||||
rdclass == dns_rdataclass_any))
|
||||
{
|
||||
DO_ERROR(DNS_R_FORMERR);
|
||||
}
|
||||
|
||||
/*
|
||||
* If this class is different than the one we already read,
|
||||
* this is an error.
|
||||
|
|
|
|||
|
|
@ -369,7 +369,16 @@ struct fetchctx {
|
|||
dns_message_t *qmessage;
|
||||
ISC_LIST(resquery_t) queries;
|
||||
dns_adbfindlist_t finds;
|
||||
dns_adbfind_t *find;
|
||||
/*
|
||||
* This is a state to keep track of the latest upstream server which is
|
||||
* being queried. See `nextaddress()`.
|
||||
*
|
||||
* `addrinfo` is basically a copy of `foundaddrinfo` but came from the
|
||||
* response of the query, so fields like the SRTT/timing might have been
|
||||
* altered. So it might be possible (?) to wrap those two in an union
|
||||
* for clarity (and memory saving).
|
||||
*/
|
||||
dns_adbaddrinfo_t *foundaddrinfo;
|
||||
/*
|
||||
* altfinds are names and/or addresses of dual stack servers that
|
||||
* should be used when iterative resolution to a server is not
|
||||
|
|
@ -1534,7 +1543,7 @@ fctx_cleanup(fetchctx_t *fctx) {
|
|||
dns_adb_destroyfind(&find);
|
||||
fctx_unref(fctx);
|
||||
}
|
||||
fctx->find = NULL;
|
||||
fctx->foundaddrinfo = NULL;
|
||||
|
||||
for (find = ISC_LIST_HEAD(fctx->altfinds); find != NULL;
|
||||
find = next_find)
|
||||
|
|
@ -3338,91 +3347,10 @@ add_bad(fetchctx_t *fctx, dns_message_t *rmessage, dns_adbaddrinfo_t *addrinfo,
|
|||
}
|
||||
|
||||
/*
|
||||
* Sort addrinfo list by RTT.
|
||||
*/
|
||||
static void
|
||||
sort_adbfind(dns_adbfind_t *find, unsigned int bias) {
|
||||
dns_adbaddrinfo_t *best, *curr;
|
||||
dns_adbaddrinfolist_t sorted;
|
||||
unsigned int best_srtt, curr_srtt;
|
||||
|
||||
/* Lame N^2 bubble sort. */
|
||||
ISC_LIST_INIT(sorted);
|
||||
while (!ISC_LIST_EMPTY(find->list)) {
|
||||
best = ISC_LIST_HEAD(find->list);
|
||||
best_srtt = best->srtt;
|
||||
if (isc_sockaddr_pf(&best->sockaddr) != AF_INET6) {
|
||||
best_srtt += bias;
|
||||
}
|
||||
curr = ISC_LIST_NEXT(best, publink);
|
||||
while (curr != NULL) {
|
||||
curr_srtt = curr->srtt;
|
||||
if (isc_sockaddr_pf(&curr->sockaddr) != AF_INET6) {
|
||||
curr_srtt += bias;
|
||||
}
|
||||
if (curr_srtt < best_srtt) {
|
||||
best = curr;
|
||||
best_srtt = curr_srtt;
|
||||
}
|
||||
curr = ISC_LIST_NEXT(curr, publink);
|
||||
}
|
||||
ISC_LIST_UNLINK(find->list, best, publink);
|
||||
ISC_LIST_APPEND(sorted, best, publink);
|
||||
}
|
||||
find->list = sorted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sort a list of finds by server RTT.
|
||||
*/
|
||||
static void
|
||||
sort_finds(dns_adbfindlist_t *findlist, unsigned int bias) {
|
||||
dns_adbfind_t *best, *curr;
|
||||
dns_adbfindlist_t sorted;
|
||||
dns_adbaddrinfo_t *addrinfo, *bestaddrinfo;
|
||||
unsigned int best_srtt, curr_srtt;
|
||||
|
||||
/* Sort each find's addrinfo list by SRTT. */
|
||||
for (curr = ISC_LIST_HEAD(*findlist); curr != NULL;
|
||||
curr = ISC_LIST_NEXT(curr, publink))
|
||||
{
|
||||
sort_adbfind(curr, bias);
|
||||
}
|
||||
|
||||
/* Lame N^2 bubble sort. */
|
||||
ISC_LIST_INIT(sorted);
|
||||
while (!ISC_LIST_EMPTY(*findlist)) {
|
||||
best = ISC_LIST_HEAD(*findlist);
|
||||
bestaddrinfo = ISC_LIST_HEAD(best->list);
|
||||
INSIST(bestaddrinfo != NULL);
|
||||
best_srtt = bestaddrinfo->srtt;
|
||||
if (isc_sockaddr_pf(&bestaddrinfo->sockaddr) != AF_INET6) {
|
||||
best_srtt += bias;
|
||||
}
|
||||
curr = ISC_LIST_NEXT(best, publink);
|
||||
while (curr != NULL) {
|
||||
addrinfo = ISC_LIST_HEAD(curr->list);
|
||||
INSIST(addrinfo != NULL);
|
||||
curr_srtt = addrinfo->srtt;
|
||||
if (isc_sockaddr_pf(&addrinfo->sockaddr) != AF_INET6) {
|
||||
curr_srtt += bias;
|
||||
}
|
||||
if (curr_srtt < best_srtt) {
|
||||
best = curr;
|
||||
best_srtt = curr_srtt;
|
||||
}
|
||||
curr = ISC_LIST_NEXT(curr, publink);
|
||||
}
|
||||
ISC_LIST_UNLINK(*findlist, best, publink);
|
||||
ISC_LIST_APPEND(sorted, best, publink);
|
||||
}
|
||||
*findlist = sorted;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true iff the ADB find has a pending fetch for 'type'. This is
|
||||
* used to find out whether we're in a loop, where a fetch is waiting for a
|
||||
* find which is waiting for that same fetch.
|
||||
* Return true iff the ADB find has an already pending fetch for 'type'. This
|
||||
* is used to find out whether we're in a loop, where a fetch is waiting for a
|
||||
* find which is waiting for that same fetch. So if the current find actually
|
||||
* started the fetch, we know it can't be a loop, so we returns false.
|
||||
*
|
||||
* Note: This could be done with either an equivalence check (e.g.,
|
||||
* query_pending == DNS_ADBFIND_INET) or with a bit check, as below. If
|
||||
|
|
@ -3529,6 +3457,7 @@ findname(fetchctx_t *fctx, const dns_name_t *name, in_port_t port,
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FCTX_ADDRINFO_DUALSTACK) != 0) {
|
||||
ISC_LIST_APPEND(fctx->altfinds, find, publink);
|
||||
} else {
|
||||
|
|
@ -3944,8 +3873,6 @@ out:
|
|||
* We've found some addresses. We might still be
|
||||
* looking for more addresses.
|
||||
*/
|
||||
sort_finds(&fctx->finds, res->view->v6bias);
|
||||
sort_finds(&fctx->altfinds, 0);
|
||||
result = ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
|
|
@ -4020,6 +3947,80 @@ possibly_mark(fetchctx_t *fctx, dns_adbaddrinfo_t *addr) {
|
|||
}
|
||||
}
|
||||
|
||||
static dns_adbaddrinfo_t *
|
||||
nextaddress(fetchctx_t *fctx) {
|
||||
dns_adbaddrinfo_t *prevai = fctx->foundaddrinfo, *lowestsrttai = NULL;
|
||||
unsigned int v6bias = fctx->res->view->v6bias, lowestsrtt = 0;
|
||||
|
||||
/*
|
||||
* Let's walk through the list of dns_adbaddrinfo_t to find the best
|
||||
* next server address to query. This is linear on the number of
|
||||
* dns_adbaddrinfo_t which are grouped in find list (for each ADB find).
|
||||
*/
|
||||
for (dns_adbfind_t *find = ISC_LIST_HEAD(fctx->finds); find != NULL;
|
||||
find = ISC_LIST_NEXT(find, publink))
|
||||
{
|
||||
for (dns_adbaddrinfo_t *ai = ISC_LIST_HEAD(find->list);
|
||||
ai != NULL; ai = ISC_LIST_NEXT(ai, publink))
|
||||
{
|
||||
/*
|
||||
* This address has been marked already, skip it.
|
||||
*/
|
||||
if (!UNMARKED(ai)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This address is the same as the previously used
|
||||
* address, it's a duplicate, mark it and skip it!
|
||||
*/
|
||||
if (prevai != NULL) {
|
||||
if (prevai->entry == ai->entry) {
|
||||
ai->flags |= FCTX_ADDRINFO_MARK;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark and skip this address if incompatible (i.e. IPv6
|
||||
* address on a v4 only server, or for ACL reason, etc.)
|
||||
*/
|
||||
possibly_mark(fctx, ai);
|
||||
if (!UNMARKED(ai)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* This address hasn't been tried yet and is a
|
||||
* good candidate. Let's keep track of it if it
|
||||
* has the lowest SRTT so far (or if there is no
|
||||
* address with lowest SRTT found yet).
|
||||
*/
|
||||
unsigned int aisrtt = ai->srtt;
|
||||
|
||||
if (isc_sockaddr_pf(&ai->sockaddr) != AF_INET6) {
|
||||
aisrtt += v6bias;
|
||||
}
|
||||
|
||||
if (lowestsrttai == NULL || aisrtt < lowestsrtt) {
|
||||
lowestsrttai = ai;
|
||||
lowestsrtt = aisrtt;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the next address to query. If this is NULL, we're done.
|
||||
*/
|
||||
if (lowestsrttai != NULL) {
|
||||
lowestsrttai->flags |= FCTX_ADDRINFO_MARK;
|
||||
}
|
||||
fctx->foundaddrinfo = lowestsrttai;
|
||||
|
||||
return lowestsrttai;
|
||||
}
|
||||
|
||||
static dns_adbaddrinfo_t *
|
||||
fctx_nextaddress(fetchctx_t *fctx) {
|
||||
dns_adbfind_t *find, *start;
|
||||
|
|
@ -4042,7 +4043,6 @@ fctx_nextaddress(fetchctx_t *fctx) {
|
|||
possibly_mark(fctx, addrinfo);
|
||||
if (UNMARKED(addrinfo)) {
|
||||
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
||||
fctx->find = NULL;
|
||||
fctx->forwarding = true;
|
||||
|
||||
/*
|
||||
|
|
@ -4063,49 +4063,9 @@ fctx_nextaddress(fetchctx_t *fctx) {
|
|||
fctx->forwarding = false;
|
||||
FCTX_ATTR_SET(fctx, FCTX_ATTR_TRIEDFIND);
|
||||
|
||||
find = fctx->find;
|
||||
if (find == NULL) {
|
||||
find = ISC_LIST_HEAD(fctx->finds);
|
||||
} else {
|
||||
find = ISC_LIST_NEXT(find, publink);
|
||||
if (find == NULL) {
|
||||
find = ISC_LIST_HEAD(fctx->finds);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find the first unmarked addrinfo.
|
||||
*/
|
||||
addrinfo = NULL;
|
||||
if (find != NULL) {
|
||||
start = find;
|
||||
do {
|
||||
for (addrinfo = ISC_LIST_HEAD(find->list);
|
||||
addrinfo != NULL;
|
||||
addrinfo = ISC_LIST_NEXT(addrinfo, publink))
|
||||
{
|
||||
if (!UNMARKED(addrinfo)) {
|
||||
continue;
|
||||
}
|
||||
possibly_mark(fctx, addrinfo);
|
||||
if (UNMARKED(addrinfo)) {
|
||||
addrinfo->flags |= FCTX_ADDRINFO_MARK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (addrinfo != NULL) {
|
||||
break;
|
||||
}
|
||||
find = ISC_LIST_NEXT(find, publink);
|
||||
if (find == NULL) {
|
||||
find = ISC_LIST_HEAD(fctx->finds);
|
||||
}
|
||||
} while (find != start);
|
||||
}
|
||||
|
||||
fctx->find = find;
|
||||
if (addrinfo != NULL) {
|
||||
return addrinfo;
|
||||
faddrinfo = nextaddress(fctx);
|
||||
if (faddrinfo != NULL) {
|
||||
return faddrinfo;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -4186,6 +4146,39 @@ fctx_nextaddress(fetchctx_t *fctx) {
|
|||
return addrinfo;
|
||||
}
|
||||
|
||||
static isc_result_t
|
||||
incr_query_counters(fetchctx_t *fctx) {
|
||||
isc_result_t result;
|
||||
|
||||
result = isc_counter_increment(fctx->qc);
|
||||
#if WANT_QUERYTRACE
|
||||
FCTXTRACE5("query", "max-recursion-queries, querycount=",
|
||||
isc_counter_used(fctx->qc));
|
||||
#endif
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
||||
"exceeded max queries resolving '%s' "
|
||||
"(max-recursion-queries, querycount=%u)",
|
||||
fctx->info, isc_counter_used(fctx->qc));
|
||||
} else if (fctx->gqc != NULL) {
|
||||
result = isc_counter_increment(fctx->gqc);
|
||||
#if WANT_QUERYTRACE
|
||||
FCTXTRACE5("query", "max-query-count, querycount=",
|
||||
isc_counter_used(fctx->gqc));
|
||||
#endif
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
||||
"exceeded global max queries resolving "
|
||||
"'%s' (max-query-count, querycount=%u)",
|
||||
fctx->info, isc_counter_used(fctx->gqc));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
|
||||
isc_result_t result;
|
||||
|
|
@ -4340,31 +4333,11 @@ fctx_try(fetchctx_t *fctx, bool retrying, bool badcache) {
|
|||
return;
|
||||
}
|
||||
|
||||
result = isc_counter_increment(fctx->qc);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
||||
"exceeded max queries resolving '%s' "
|
||||
"(max-recursion-queries, querycount=%u)",
|
||||
fctx->info, isc_counter_used(fctx->qc));
|
||||
fctx_done_detach(&fctx, DNS_R_SERVFAIL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fctx->gqc != NULL) {
|
||||
result = isc_counter_increment(fctx->gqc);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(dns_lctx, DNS_LOGCATEGORY_RESOLVER,
|
||||
DNS_LOGMODULE_RESOLVER, ISC_LOG_DEBUG(3),
|
||||
"exceeded global max queries resolving "
|
||||
"'%s' (max-query-count, querycount=%u)",
|
||||
fctx->info, isc_counter_used(fctx->gqc));
|
||||
fctx_done_detach(&fctx, DNS_R_SERVFAIL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
CHECK(incr_query_counters(fctx));
|
||||
|
||||
result = fctx_query(fctx, addrinfo, fctx->options);
|
||||
|
||||
cleanup:
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
fctx_done_detach(&fctx, result);
|
||||
} else if (retrying) {
|
||||
|
|
@ -7343,6 +7316,13 @@ is_answeraddress_allowed(dns_view_t *view, dns_name_t *name,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* deny-answer-address doesn't apply to non-IN classes.
|
||||
*/
|
||||
if (rdataset->rdclass != dns_rdataclass_in) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Otherwise, search the filter list for a match for each
|
||||
* address record. If a match is found, the address should be
|
||||
|
|
@ -10075,9 +10055,9 @@ rctx_nextserver(respctx_t *rctx, dns_message_t *message,
|
|||
* rctx_resend():
|
||||
*
|
||||
* Resend the query, probably with the options changed. Calls
|
||||
* fctx_query(), passing rctx->retryopts (which is based on
|
||||
* query->options, but may have been updated since the last time
|
||||
* fctx_query() was called).
|
||||
* fctx_query(), unless query counter limits are hit, passing
|
||||
* rctx->retryopts (which is based on query->options, but may have
|
||||
* been updated since the last time fctx_query() was called).
|
||||
*/
|
||||
static void
|
||||
rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
|
||||
|
|
@ -10085,8 +10065,15 @@ rctx_resend(respctx_t *rctx, dns_adbaddrinfo_t *addrinfo) {
|
|||
isc_result_t result;
|
||||
|
||||
FCTXTRACE("resend");
|
||||
inc_stats(fctx->res, dns_resstatscounter_retry);
|
||||
|
||||
CHECK(incr_query_counters(fctx));
|
||||
|
||||
result = fctx_query(fctx, addrinfo, rctx->retryopts);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
inc_stats(fctx->res, dns_resstatscounter_retry);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
fctx_done_detach(&rctx->fctx, result);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -528,19 +528,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin,
|
|||
return ISC_R_SUCCESS;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXXDCL need to check for key expiry per 4.1.1
|
||||
* XXXDCL need a way to check fully established, perhaps w/key_flags
|
||||
*/
|
||||
|
||||
intoken.base = tkeyin->key;
|
||||
intoken.length = tkeyin->keylen;
|
||||
|
||||
result = dns_tsigkey_find(&tsigkey, name, &tkeyin->algorithm, ring);
|
||||
if (result == ISC_R_SUCCESS) {
|
||||
gss_ctx = dst_key_getgssctx(tsigkey->key);
|
||||
}
|
||||
|
||||
principal = dns_fixedname_initname(&fixed);
|
||||
|
||||
/*
|
||||
|
|
@ -549,28 +539,27 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin,
|
|||
result = dst_gssapi_acceptctx(tctx->gsscred, tctx->gssapi_keytab,
|
||||
&intoken, &outtoken, &gss_ctx, principal,
|
||||
tctx->mctx);
|
||||
if (result == DNS_R_INVALIDTKEY) {
|
||||
if (tsigkey != NULL) {
|
||||
dns_tsigkey_detach(&tsigkey);
|
||||
}
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
tkeyout->error = dns_tsigerror_badkey;
|
||||
tkey_log("process_gsstkey(): dns_tsigerror_badkey"); /* XXXSRA
|
||||
*/
|
||||
return ISC_R_SUCCESS;
|
||||
}
|
||||
if (result != DNS_R_CONTINUE && result != ISC_R_SUCCESS) {
|
||||
CHECK(result);
|
||||
tkey_log("process_gsstkey(): dns_tsigerror_badkey");
|
||||
result = ISC_R_SUCCESS;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXXDCL Section 4.1.3: Limit GSS_S_CONTINUE_NEEDED to 10 times.
|
||||
* Multi-round GSS-API negotiation (GSS_S_CONTINUE_NEEDED) is
|
||||
* rejected in dst_gssapi_acceptctx(), so if we reach here the
|
||||
* negotiation is complete and the principal must be set.
|
||||
*/
|
||||
|
||||
isc_stdtime_get(&now);
|
||||
|
||||
if (dns_name_countlabels(principal) == 0U) {
|
||||
if (tsigkey != NULL) {
|
||||
dns_tsigkey_detach(&tsigkey);
|
||||
}
|
||||
tkeyout->error = dns_tsigerror_badkey;
|
||||
tkey_log("process_gsstkey(): "
|
||||
"completed context with empty principal");
|
||||
result = ISC_R_SUCCESS;
|
||||
goto cleanup;
|
||||
} else if (tsigkey == NULL) {
|
||||
#if HAVE_GSSAPI
|
||||
OM_uint32 gret, minor, lifetime;
|
||||
|
|
@ -633,6 +622,9 @@ process_gsstkey(dns_message_t *msg, dns_name_t *name, dns_rdata_tkey_t *tkeyin,
|
|||
return ISC_R_SUCCESS;
|
||||
|
||||
cleanup:
|
||||
if (dstkey == NULL && gss_ctx != NULL) {
|
||||
dst_gssapi_deletectx(tctx->mctx, &gss_ctx);
|
||||
}
|
||||
if (tsigkey != NULL) {
|
||||
dns_tsigkey_detach(&tsigkey);
|
||||
}
|
||||
|
|
@ -645,9 +637,9 @@ cleanup:
|
|||
isc_buffer_free(&outtoken);
|
||||
}
|
||||
|
||||
tkey_log("process_gsstkey(): %s", isc_result_totext(result)); /* XXXSRA
|
||||
*/
|
||||
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
tkey_log("process_gsstkey(): %s", isc_result_totext(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -1546,9 +1538,8 @@ dns_tkey_gssnegotiate(dns_message_t *qmsg, dns_message_t *rmsg,
|
|||
NULL));
|
||||
|
||||
/*
|
||||
* XXXSRA This seems confused. If we got CONTINUE from initctx,
|
||||
* the GSS negotiation hasn't completed yet, so we can't sign
|
||||
* anything yet.
|
||||
* GSS negotiation is complete (CONTINUE returned earlier).
|
||||
* Create the TSIG key from the established context.
|
||||
*/
|
||||
|
||||
CHECK(dns_tsigkey_createfromkey(
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include <isc/once.h>
|
||||
#include <isc/os.h>
|
||||
#include <isc/print.h>
|
||||
#include <isc/random.h>
|
||||
#include <isc/refcount.h>
|
||||
#include <isc/string.h>
|
||||
#include <isc/types.h>
|
||||
|
|
@ -151,7 +152,6 @@ struct isc_mem {
|
|||
atomic_size_t malloced;
|
||||
atomic_size_t maxmalloced;
|
||||
atomic_bool hi_called;
|
||||
atomic_bool is_overmem;
|
||||
isc_mem_water_t water;
|
||||
void *water_arg;
|
||||
atomic_size_t hi_water;
|
||||
|
|
@ -534,7 +534,6 @@ mem_create(isc_mem_t **ctxp, unsigned int flags, unsigned int jemalloc_flags) {
|
|||
atomic_init(&ctx->hi_water, 0);
|
||||
atomic_init(&ctx->lo_water, 0);
|
||||
atomic_init(&ctx->hi_called, false);
|
||||
atomic_init(&ctx->is_overmem, false);
|
||||
|
||||
for (size_t i = 0; i < STATS_BUCKETS + 1; i++) {
|
||||
atomic_init(&ctx->stats[i].gets, 0);
|
||||
|
|
@ -786,9 +785,6 @@ hi_water(isc_mem_t *ctx) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* We are over water (for the first time) */
|
||||
atomic_store_release(&ctx->is_overmem, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -810,9 +806,6 @@ lo_water(isc_mem_t *ctx) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* We are no longer overmem */
|
||||
atomic_store_release(&ctx->is_overmem, false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1195,7 +1188,30 @@ bool
|
|||
isc_mem_isovermem(isc_mem_t *ctx) {
|
||||
REQUIRE(VALID_CONTEXT(ctx));
|
||||
|
||||
return atomic_load_relaxed(&ctx->is_overmem);
|
||||
size_t hiwater = atomic_load_relaxed(&ctx->hi_water);
|
||||
if (hiwater == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t inuse = atomic_load_relaxed(&ctx->inuse);
|
||||
if (inuse >= hiwater) {
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t lowater = atomic_load_relaxed(&ctx->lo_water);
|
||||
if (inuse <= lowater) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Between lo_water and hi_water, return true with a probability
|
||||
* that ramps linearly from 0 at lo_water to 1 at hi_water. This
|
||||
* spreads cache cleaning across many inserts instead of triggering
|
||||
* a thundering herd once the hi_water mark is crossed.
|
||||
*/
|
||||
uint32_t prob = (uint32_t)(((uint64_t)(inuse - lowater) * 256) /
|
||||
(hiwater - lowater));
|
||||
return isc_random8() < prob;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@
|
|||
#include <dns/dispatch.h>
|
||||
#include <dns/dnstap.h>
|
||||
#include <dns/edns.h>
|
||||
#include <dns/enumclass.h>
|
||||
#include <dns/events.h>
|
||||
#include <dns/message.h>
|
||||
#include <dns/peer.h>
|
||||
|
|
@ -2083,7 +2084,9 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
|
|||
}
|
||||
}
|
||||
|
||||
if (client->message->rdclass == 0) {
|
||||
char classbuf[DNS_RDATACLASS_FORMATSIZE];
|
||||
switch (client->message->rdclass) {
|
||||
case dns_rdataclass_reserved0:
|
||||
if ((client->attributes & NS_CLIENTATTR_WANTCOOKIE) != 0 &&
|
||||
client->message->opcode == dns_opcode_query &&
|
||||
client->message->counts[DNS_SECTION_QUESTION] == 0U)
|
||||
|
|
@ -2102,12 +2105,46 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
|
|||
return;
|
||||
}
|
||||
|
||||
ns_client_dumpmessage(client,
|
||||
"message class could not be determined");
|
||||
ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
|
||||
return;
|
||||
case dns_rdataclass_in:
|
||||
break;
|
||||
case dns_rdataclass_chaos:
|
||||
break;
|
||||
case dns_rdataclass_hs:
|
||||
break;
|
||||
case dns_rdataclass_none:
|
||||
if (client->message->opcode != dns_opcode_update) {
|
||||
ns_client_dumpmessage(client,
|
||||
"message class NONE can be only "
|
||||
"used in DNS updates");
|
||||
ns_client_error(client, DNS_R_FORMERR);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case dns_rdataclass_any:
|
||||
/*
|
||||
* Required for TKEY negotiation.
|
||||
*/
|
||||
if (client->message->tkey == 0) {
|
||||
ns_client_dumpmessage(client,
|
||||
"message class ANY can be only "
|
||||
"used for TKEY negotiation");
|
||||
ns_client_error(client, DNS_R_FORMERR);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dns_rdataclass_format(client->message->rdclass, classbuf,
|
||||
sizeof(classbuf));
|
||||
ns_client_dumpmessage(client, "");
|
||||
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
|
||||
NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
|
||||
"message class could not be determined");
|
||||
ns_client_dumpmessage(client, "message class could not be "
|
||||
"determined");
|
||||
ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_FORMERR);
|
||||
"invalid message class: %s", classbuf);
|
||||
|
||||
ns_client_error(client, DNS_R_NOTIMP);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2140,7 +2177,7 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
|
|||
ns_client_log(client, NS_LOGCATEGORY_CLIENT,
|
||||
NS_LOGMODULE_CLIENT, ISC_LOG_DEBUG(1),
|
||||
"no matching view in class '%s'", classname);
|
||||
ns_client_dumpmessage(client, "no matching view in class");
|
||||
ns_client_dumpmessage(client, "");
|
||||
ns_client_extendederror(client, DNS_EDE_PROHIBITED, NULL);
|
||||
ns_client_error(client, notimp ? DNS_R_NOTIMP : DNS_R_REFUSED);
|
||||
return;
|
||||
|
|
@ -2337,6 +2374,10 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
|
|||
break;
|
||||
case dns_opcode_update:
|
||||
CTRACE("update");
|
||||
if (client->view->rdclass != dns_rdataclass_in) {
|
||||
ns_client_error(client, DNS_R_NOTIMP);
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_DNSTAP
|
||||
dns_dt_send(client->view, DNS_DTTYPE_UQ, &client->peeraddr,
|
||||
&client->destsockaddr, TCP_CLIENT(client), NULL,
|
||||
|
|
@ -2347,6 +2388,10 @@ ns__client_request(isc_nmhandle_t *handle, isc_result_t eresult,
|
|||
break;
|
||||
case dns_opcode_notify:
|
||||
CTRACE("notify");
|
||||
if (client->view->rdclass != dns_rdataclass_in) {
|
||||
ns_client_error(client, DNS_R_NOTIMP);
|
||||
break;
|
||||
}
|
||||
ns_client_settimeout(client, 60);
|
||||
ns_notify_start(client, handle);
|
||||
break;
|
||||
|
|
@ -2773,7 +2818,7 @@ ns_client_dumpmessage(ns_client_t *client, const char *reason) {
|
|||
int len = 1024;
|
||||
isc_result_t result;
|
||||
|
||||
if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1))) {
|
||||
if (!isc_log_wouldlog(ns_lctx, ISC_LOG_DEBUG(1)) || reason == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -999,7 +999,9 @@ ssu_checkrr(void *data, rr_t *rr) {
|
|||
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
||||
target = &ptr.ptr;
|
||||
}
|
||||
if (rr->rdata.type == dns_rdatatype_srv) {
|
||||
if (rr->rdata.rdclass == dns_rdataclass_in &&
|
||||
rr->rdata.type == dns_rdatatype_srv)
|
||||
{
|
||||
result = dns_rdata_tostruct(&rr->rdata, &srv, NULL);
|
||||
RUNTIME_CHECK(result == ISC_R_SUCCESS);
|
||||
target = &srv.target;
|
||||
|
|
@ -1354,7 +1356,10 @@ replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
if (db_rr->type == dns_rdatatype_wks) {
|
||||
|
||||
if (db_rr->rdclass == dns_rdataclass_in &&
|
||||
db_rr->type == dns_rdatatype_wks)
|
||||
{
|
||||
/*
|
||||
* Compare the address and protocol fields only. These
|
||||
* form the first five bytes of the RR data. Do a
|
||||
|
|
@ -1497,8 +1502,7 @@ cleanup:
|
|||
* 'rdata', and 'ttl', respectively.
|
||||
*/
|
||||
static void
|
||||
get_current_rr(dns_message_t *msg, dns_section_t section,
|
||||
dns_rdataclass_t zoneclass, dns_name_t **name,
|
||||
get_current_rr(dns_message_t *msg, dns_section_t section, dns_name_t **name,
|
||||
dns_rdata_t *rdata, dns_rdatatype_t *covers, dns_ttl_t *ttl,
|
||||
dns_rdataclass_t *update_class) {
|
||||
dns_rdataset_t *rdataset;
|
||||
|
|
@ -1514,7 +1518,7 @@ get_current_rr(dns_message_t *msg, dns_section_t section,
|
|||
dns_rdataset_current(rdataset, rdata);
|
||||
INSIST(dns_rdataset_next(rdataset) == ISC_R_NOMORE);
|
||||
*update_class = rdata->rdclass;
|
||||
rdata->rdclass = zoneclass;
|
||||
rdata->rdclass = dns_rdataclass_in;
|
||||
}
|
||||
|
||||
/*%
|
||||
|
|
@ -1616,7 +1620,6 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
|
|||
dns_message_t *request = client->message;
|
||||
isc_mem_t *mctx = client->manager->mctx;
|
||||
dns_aclenv_t *env = client->manager->aclenv;
|
||||
dns_rdataclass_t zoneclass;
|
||||
dns_rdatatype_t covers;
|
||||
dns_name_t *zonename = NULL;
|
||||
unsigned int *maxbytype = NULL;
|
||||
|
|
@ -1626,10 +1629,12 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
|
|||
|
||||
CHECK(dns_zone_getdb(zone, &db));
|
||||
zonename = dns_db_origin(db);
|
||||
zoneclass = dns_db_class(db);
|
||||
dns_zone_getssutable(zone, &ssutable);
|
||||
dns_db_currentversion(db, &ver);
|
||||
|
||||
/* Updates are only supported for class IN. */
|
||||
INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
|
||||
|
||||
/*
|
||||
* Update message processing can leak record existence information
|
||||
* so check that we are allowed to query this zone. Additionally,
|
||||
|
|
@ -1680,13 +1685,13 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
|
|||
|
||||
INSIST(ssutable == NULL || update < maxbytypelen);
|
||||
|
||||
get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
|
||||
&rdata, &covers, &ttl, &update_class);
|
||||
get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
|
||||
&covers, &ttl, &update_class);
|
||||
|
||||
if (!dns_name_issubdomain(name, zonename)) {
|
||||
FAILC(DNS_R_NOTZONE, "update RR is outside zone");
|
||||
}
|
||||
if (update_class == zoneclass) {
|
||||
if (update_class == dns_rdataclass_in) {
|
||||
/*
|
||||
* Check for meta-RRs. The RFC2136 pseudocode says
|
||||
* check for ANY|AXFR|MAILA|MAILB, but the text adds
|
||||
|
|
@ -1776,7 +1781,6 @@ send_update_event(ns_client_t *client, dns_zone_t *zone) {
|
|||
}
|
||||
|
||||
if (update_class == dns_rdataclass_any &&
|
||||
zoneclass == dns_rdataclass_in &&
|
||||
(rdata.type == dns_rdatatype_ptr ||
|
||||
rdata.type == dns_rdatatype_srv))
|
||||
{
|
||||
|
|
@ -2860,7 +2864,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
|
|||
isc_mem_t *mctx = client->mctx;
|
||||
dns_rdatatype_t covers;
|
||||
dns_message_t *request = client->message;
|
||||
dns_rdataclass_t zoneclass;
|
||||
dns_name_t *zonename = NULL;
|
||||
dns_fixedname_t tmpnamefixed;
|
||||
dns_name_t *tmpname = NULL;
|
||||
|
|
@ -2880,9 +2883,9 @@ update_action(isc_task_t *task, isc_event_t *event) {
|
|||
|
||||
CHECK(dns_zone_getdb(zone, &db));
|
||||
zonename = dns_db_origin(db);
|
||||
zoneclass = dns_db_class(db);
|
||||
options = dns_zone_getoptions(zone);
|
||||
|
||||
INSIST(dns_zone_getclass(zone) == dns_rdataclass_in);
|
||||
/*
|
||||
* Get old and new versions now that queryacl has been checked.
|
||||
*/
|
||||
|
|
@ -2903,8 +2906,8 @@ update_action(isc_task_t *task, isc_event_t *event) {
|
|||
dns_rdataclass_t update_class;
|
||||
bool flag;
|
||||
|
||||
get_current_rr(request, DNS_SECTION_PREREQUISITE, zoneclass,
|
||||
&name, &rdata, &covers, &ttl, &update_class);
|
||||
get_current_rr(request, DNS_SECTION_PREREQUISITE, &name, &rdata,
|
||||
&covers, &ttl, &update_class);
|
||||
|
||||
if (ttl != 0) {
|
||||
PREREQFAILC(DNS_R_FORMERR,
|
||||
|
|
@ -2967,7 +2970,7 @@ update_action(isc_task_t *task, isc_event_t *event) {
|
|||
"prerequisite not satisfied");
|
||||
}
|
||||
}
|
||||
} else if (update_class == zoneclass) {
|
||||
} else if (update_class == dns_rdataclass_in) {
|
||||
/* "temp<rr.name, rr.type> += rr;" */
|
||||
result = temp_append(&temp, name, &rdata);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
|
|
@ -3029,10 +3032,10 @@ update_action(isc_task_t *task, isc_event_t *event) {
|
|||
|
||||
INSIST(ssutable == NULL || update < maxbytypelen);
|
||||
|
||||
get_current_rr(request, DNS_SECTION_UPDATE, zoneclass, &name,
|
||||
&rdata, &covers, &ttl, &update_class);
|
||||
get_current_rr(request, DNS_SECTION_UPDATE, &name, &rdata,
|
||||
&covers, &ttl, &update_class);
|
||||
|
||||
if (update_class == zoneclass) {
|
||||
if (update_class == dns_rdataclass_in) {
|
||||
/*
|
||||
* RFC1123 doesn't allow MF and MD in master files.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -757,16 +757,6 @@ ns_xfr_start(ns_client_t *client, dns_rdatatype_t reqtype) {
|
|||
|
||||
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT, NS_LOGMODULE_XFER_OUT,
|
||||
ISC_LOG_DEBUG(6), "%s request", mnemonic);
|
||||
/*
|
||||
* Apply quota.
|
||||
*/
|
||||
result = isc_quota_attach(&client->sctx->xfroutquota, "a);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(XFROUT_COMMON_LOGARGS, ISC_LOG_WARNING,
|
||||
"%s request denied: %s", mnemonic,
|
||||
isc_result_totext(result));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interpret the question section.
|
||||
|
|
@ -938,6 +928,18 @@ got_soa:
|
|||
FAILC(DNS_R_FORMERR, "attempted AXFR over UDP");
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply quota after ACL is checked, so that unauthorized clients
|
||||
* can not starve the authorized clients.
|
||||
*/
|
||||
result = isc_quota_attach(&client->sctx->xfroutquota, "a);
|
||||
if (result != ISC_R_SUCCESS) {
|
||||
isc_log_write(XFROUT_COMMON_LOGARGS, ISC_LOG_WARNING,
|
||||
"%s request denied: %s", mnemonic,
|
||||
isc_result_totext(result));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up the requesting server in the peer table.
|
||||
*/
|
||||
|
|
@ -1194,6 +1196,7 @@ cleanup:
|
|||
}
|
||||
/* XXX kludge */
|
||||
if (xfr != NULL) {
|
||||
/* The quota will be released in xfrout_ctx_destroy(). */
|
||||
xfrout_fail(xfr, result, "setting up zone transfer");
|
||||
} else if (result != ISC_R_SUCCESS) {
|
||||
ns_client_log(client, DNS_LOGCATEGORY_XFER_OUT,
|
||||
|
|
|
|||
|
|
@ -307,7 +307,6 @@ ISC_RUN_TEST_IMPL(overmempurge_bigrdata) {
|
|||
for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
|
||||
overmempurge_addrdataset(db, now, i, 50053, 0, false);
|
||||
}
|
||||
assert_true(isc_mem_isovermem(mctx2));
|
||||
|
||||
/*
|
||||
* Then try to add the same number of entries, each has very large data.
|
||||
|
|
@ -353,7 +352,6 @@ ISC_RUN_TEST_IMPL(overmempurge_longname) {
|
|||
for (i = 0; !isc_mem_isovermem(mctx2) && i < (maxcache / 10); i++) {
|
||||
overmempurge_addrdataset(db, now, i, 50053, 0, false);
|
||||
}
|
||||
assert_true(isc_mem_isovermem(mctx2));
|
||||
|
||||
/*
|
||||
* Then try to add the same number of entries, each has very large data.
|
||||
|
|
|
|||
|
|
@ -290,6 +290,57 @@ ISC_RUN_TEST_IMPL(isc_mem_reget) {
|
|||
isc_mem_put(mctx, data, REGET_SHRINK_SIZE);
|
||||
}
|
||||
|
||||
static bool
|
||||
at_least_one_overmem(isc_mem_t *omctx) {
|
||||
for (size_t i = 0; i < UINT16_MAX; i++) {
|
||||
/* The overmem is probability based in this range */
|
||||
if (isc_mem_isovermem(omctx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
water(void *arg, int mark) {
|
||||
UNUSED(arg);
|
||||
UNUSED(mark);
|
||||
}
|
||||
|
||||
ISC_RUN_TEST_IMPL(isc_mem_overmem) {
|
||||
isc_mem_t *omctx = NULL;
|
||||
isc_mem_create(&omctx);
|
||||
assert_non_null(omctx);
|
||||
|
||||
isc_mem_setwater(omctx, water, NULL, 1024, 512);
|
||||
|
||||
/* inuse <= lo_water is always false */
|
||||
void *data1 = isc_mem_allocate(omctx, 256);
|
||||
assert_false(isc_mem_isovermem(omctx));
|
||||
|
||||
/* lo_water < inuse < hi_water might be true or false */
|
||||
void *data2 = isc_mem_allocate(omctx, 512);
|
||||
assert_true(at_least_one_overmem(omctx));
|
||||
|
||||
/* hi_water <= inuse is always true */
|
||||
void *data3 = isc_mem_allocate(omctx, 512);
|
||||
assert_true(isc_mem_isovermem(omctx));
|
||||
|
||||
/* lo_water < inuse < hi_water might be true or false */
|
||||
isc_mem_free(omctx, data2);
|
||||
assert_true(at_least_one_overmem(omctx));
|
||||
|
||||
/* inuse <= lo_water is always false */
|
||||
isc_mem_free(omctx, data3);
|
||||
assert_false(isc_mem_isovermem(omctx));
|
||||
|
||||
/* inuse == 0 is always false */
|
||||
isc_mem_free(omctx, data1);
|
||||
assert_false(isc_mem_isovermem(omctx));
|
||||
|
||||
isc_mem_destroy(&omctx);
|
||||
}
|
||||
|
||||
#if ISC_MEM_TRACKLINES
|
||||
|
||||
/* test mem with no flags */
|
||||
|
|
@ -501,6 +552,7 @@ ISC_TEST_ENTRY(isc_mem_total)
|
|||
ISC_TEST_ENTRY(isc_mem_inuse)
|
||||
ISC_TEST_ENTRY(isc_mem_zeroget)
|
||||
ISC_TEST_ENTRY(isc_mem_reget)
|
||||
ISC_TEST_ENTRY(isc_mem_overmem)
|
||||
|
||||
#if !defined(__SANITIZE_THREAD__)
|
||||
ISC_TEST_ENTRY(isc_mem_benchmark)
|
||||
|
|
|
|||
Loading…
Reference in a new issue