diff options
Diffstat (limited to '')
21 files changed, 1754 insertions, 0 deletions
diff --git a/bin/tests/system/chain/README b/bin/tests/system/chain/README new file mode 100644 index 0000000..649142e --- /dev/null +++ b/bin/tests/system/chain/README @@ -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. + +ns1 is the root server. + +ns2 and ns5 are both authoritative servers. + +ans3 is a mock authoritative server that can return various broken +responses. + +ans4 is a mock authoritative server that can return CNAME or DNAME +responses of arbitrary size in arbitrary order. + +ns7 is the resolver under test. diff --git a/bin/tests/system/chain/ans3/ans.pl b/bin/tests/system/chain/ans3/ans.pl new file mode 100644 index 0000000..271b2a4 --- /dev/null +++ b/bin/tests/system/chain/ans3/ans.pl @@ -0,0 +1,131 @@ +#!/usr/bin/env perl + +# 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. + +use strict; +use warnings; + +use IO::File; +use Getopt::Long; +use Net::DNS::Nameserver; + +my $pidf = new IO::File "ans.pid", "w" or die "cannot open pid file: $!"; +print $pidf "$$\n" or die "cannot write pid file: $!"; +$pidf->close or die "cannot close pid file: $!"; +sub rmpid { unlink "ans.pid"; exit 1; }; + +$SIG{INT} = \&rmpid; +$SIG{TERM} = \&rmpid; + +my $localaddr = "10.53.0.3"; + +my $localport = int($ENV{'PORT'}); +if (!$localport) { $localport = 5300; } + +my $verbose = 0; +my $ttl = 60; +my $zone = "example.broken"; +my $nsname = "ns3.$zone"; +my $synth = "synth-then-dname.$zone"; +my $synth2 = "synth2-then-dname.$zone"; + +sub reply_handler { + my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_; + my ($rcode, @ans, @auth, @add); + + print ("request: $qname/$qtype\n"); + STDOUT->flush(); + + if ($qname eq "example.broken") { + if ($qtype eq "SOA") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass SOA . . 0 0 0 0 0"); + push @ans, $rr; + } elsif ($qtype eq "NS") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass NS $nsname"); + push @ans, $rr; + $rr = new Net::DNS::RR("$nsname $ttl $qclass A $localaddr"); + push @add, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "cname-to-$synth2") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name.$synth2"); + push @ans, $rr; + $rr = new Net::DNS::RR("name.$synth2 $ttl $qclass CNAME name"); + push @ans, $rr; + $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); + push @ans, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "$synth" || $qname eq "$synth2") { + if ($qtype eq "DNAME") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME ."); + push @ans, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "name.$synth") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); + push @ans, $rr; + $rr = new Net::DNS::RR("$synth $ttl $qclass DNAME ."); + push @ans, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "name.$synth2") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME name."); + push @ans, $rr; + $rr = new Net::DNS::RR("$synth2 $ttl $qclass DNAME ."); + push @ans, $rr; + $rcode = "NOERROR"; + # The following three code branches referring to the "example.dname" + # zone are necessary for the resolver variant of the CVE-2021-25215 + # regression test to work. A named instance cannot be used for + # serving the DNAME records below as a version of BIND vulnerable to + # CVE-2021-25215 would crash while answering the queries asked by + # the tested resolver. + } elsif ($qname eq "ns3.example.dname") { + if ($qtype eq "A") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass A 10.53.0.3"); + push @ans, $rr; + } + if ($qtype eq "AAAA") { + my $rr = new Net::DNS::RR("example.dname. $ttl $qclass SOA . . 0 0 0 0 $ttl"); + push @auth, $rr; + } + $rcode = "NOERROR"; + } elsif ($qname eq "self.example.self.example.dname") { + my $rr = new Net::DNS::RR("self.example.dname. $ttl $qclass DNAME dname."); + push @ans, $rr; + $rr = new Net::DNS::RR("$qname $ttl $qclass CNAME self.example.dname."); + push @ans, $rr; + $rcode = "NOERROR"; + } elsif ($qname eq "self.example.dname") { + if ($qtype eq "DNAME") { + my $rr = new Net::DNS::RR("$qname $ttl $qclass DNAME dname."); + push @ans, $rr; + } + $rcode = "NOERROR"; + } else { + $rcode = "REFUSED"; + } + return ($rcode, \@ans, \@auth, \@add, { aa => 1 }); +} + +GetOptions( + 'port=i' => \$localport, + 'verbose!' => \$verbose, +); + +my $ns = Net::DNS::Nameserver->new( + LocalAddr => $localaddr, + LocalPort => $localport, + ReplyHandler => \&reply_handler, + Verbose => $verbose, +); + +$ns->main_loop; diff --git a/bin/tests/system/chain/ans4/README.anspy b/bin/tests/system/chain/ans4/README.anspy new file mode 100644 index 0000000..7cb0bf0 --- /dev/null +++ b/bin/tests/system/chain/ans4/README.anspy @@ -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. + +REQUIREMENTS +ans.py requires at least dnspython 1.12.0. + +"ans.py" is a fairly simple Python script that will respond as an +authoritative server to DNS queries. It opens a UDP socket on 10.53.0.4 +and fd92:7065:b8e:ffff::8, port 5300 (or PORT) (these are for DNS queries) +and a TCP socket addresses on 10.53.0.4 at port 5301 (or EXTRAPORT1) +(this is the control channel). + +Please note that all functionality and formatting are subject to change as +we determine what features the tool will need. + +"ans.py" will respond to queries as follows: TBD diff --git a/bin/tests/system/chain/ans4/ans.py b/bin/tests/system/chain/ans4/ans.py new file mode 100755 index 0000000..839067f --- /dev/null +++ b/bin/tests/system/chain/ans4/ans.py @@ -0,0 +1,386 @@ +# 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. + +############################################################################ +# ans.py: See README.anspy for details. +############################################################################ + +from __future__ import print_function +import os +import sys +import signal +import socket +import select +from datetime import datetime, timedelta +import functools + +import dns, dns.message, dns.query +from dns.rdatatype import * +from dns.rdataclass import * +from dns.rcode import * +from dns.name import * + +############################################################################ +# set up the RRs to be returned in the next answer +# +# the message contains up to two pipe-separated ('|') fields. +# +# the first field of the message is a comma-separated list +# of actions indicating what to put into the answer set +# (e.g., a dname, a cname, another cname, etc) +# +# supported actions: +# - cname (cname from the current name to a new one in the same domain) +# - dname (dname to a new domain, plus a synthesized cname) +# - xname ("external" cname, to a new name in a new domain) +# +# example: xname, dname, cname represents a CNAME to an external +# domain which is then answered by a DNAME and synthesized +# CNAME pointing to yet another domain, which is then answered +# by a CNAME within the same domain, and finally an answer +# to the query. each RR in the answer set has a corresponding +# RRSIG. these signatures are not valid, but will exercise the +# response parser. +# +# the second field is a comma-separated list of which RRs in the +# answer set to include in the answer, in which order. if prepended +# with 's', the number indicates which signature to include. +# +# examples: for the answer set "cname, cname, cname", an rr set +# '1, s1, 2, s2, 3, s3, 4, s4' indicates that all four RRs should +# be included in the answer, with siagntures, in the original +# order, while 4, s4, 3, s3, 2, s2, 1, s1' indicates the order +# should be reversed, 's3, s3, s3, s3' indicates that the third +# RRSIG should be repeated four times and everything else should +# be omitted, and so on. +# +# if there is no second field (i.e., no pipe symbol appears in +# the line) , the default is to send all answers and signatures. +# if a pipe symbol exists but the second field is empty, then +# nothing is sent at all. +############################################################################ +actions = [] +rrs = [] + + +def ctl_channel(msg): + global actions, rrs + + msg = msg.splitlines().pop(0) + print("received control message: %s" % msg) + + msg = msg.split(b"|") + if len(msg) == 0: + return + + actions = [x.strip() for x in msg[0].split(b",")] + n = functools.reduce( + lambda n, act: (n + (2 if act == b"dname" else 1)), [0] + actions + ) + + if len(msg) == 1: + rrs = [] + for i in range(n): + for b in [False, True]: + rrs.append((i, b)) + return + + rlist = [x.strip() for x in msg[1].split(b",")] + rrs = [] + for item in rlist: + if item[0] == b"s"[0]: + i = int(item[1:].strip()) - 1 + if i > n: + print("invalid index %d" + (i + 1)) + continue + rrs.append((int(item[1:]) - 1, True)) + else: + i = int(item) - 1 + if i > n: + print("invalid index %d" % (i + 1)) + continue + rrs.append((i, False)) + + +############################################################################ +# Respond to a DNS query. +############################################################################ +def create_response(msg): + m = dns.message.from_wire(msg) + qname = m.question[0].name.to_text() + labels = qname.lower().split(".") + wantsigs = True if m.ednsflags & dns.flags.DO else False + + # get qtype + rrtype = m.question[0].rdtype + typename = dns.rdatatype.to_text(rrtype) + + # for 'www.example.com.'... + # - name is 'www' + # - domain is 'example.com.' + # - sld is 'example' + # - tld is 'com.' + name = labels.pop(0) + domain = ".".join(labels) + sld = labels.pop(0) + tld = ".".join(labels) + + print("query: " + qname + "/" + typename) + print("domain: " + domain) + + # default answers, depending on QTYPE. + # currently only A, AAAA, TXT and NS are supported. + ttl = 86400 + additionalA = "10.53.0.4" + additionalAAAA = "fd92:7065:b8e:ffff::4" + if typename == "A": + final = "10.53.0.4" + elif typename == "AAAA": + final = "fd92:7065:b8e:ffff::4" + elif typename == "TXT": + final = "Some\ text\ here" + elif typename == "NS": + domain = qname + final = "ns1.%s" % domain + else: + final = None + + # RRSIG rdata - won't validate but will exercise response parsing + t = datetime.now() + delta = timedelta(30) + t1 = t - delta + t2 = t + delta + inception = t1.strftime("%Y%m%d000000") + expiry = t2.strftime("%Y%m%d000000") + sigdata = "OCXH2De0yE4NMTl9UykvOsJ4IBGs/ZIpff2rpaVJrVG7jQfmj50otBAp A0Zo7dpBU4ofv0N/F2Ar6LznCncIojkWptEJIAKA5tHegf/jY39arEpO cevbGp6DKxFhlkLXNcw7k9o7DSw14OaRmgAjXdTFbrl4AiAa0zAttFko Tso=" + + # construct answer set. + answers = [] + sigs = [] + curdom = domain + curname = name + i = 0 + + for action in actions: + if name != "test": + continue + if action == b"xname": + owner = curname + "." + curdom + newname = "cname%d" % i + i += 1 + newdom = "domain%d.%s" % (i, tld) + i += 1 + target = newname + "." + newdom + print("add external CNAME %s to %s" % (owner, target)) + answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target)) + rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % ( + ttl, + expiry, + inception, + domain, + sigdata, + ) + print("add external RRISG(CNAME) %s to %s" % (owner, target)) + sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig)) + curname = newname + curdom = newdom + continue + + if action == b"cname": + owner = curname + "." + curdom + newname = "cname%d" % i + target = newname + "." + curdom + i += 1 + print("add CNAME %s to %s" % (owner, target)) + answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target)) + rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % ( + ttl, + expiry, + inception, + domain, + sigdata, + ) + print("add RRSIG(CNAME) %s to %s" % (owner, target)) + sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig)) + curname = newname + continue + + if action == b"dname": + owner = curdom + newdom = "domain%d.%s" % (i, tld) + i += 1 + print("add DNAME %s to %s" % (owner, newdom)) + answers.append(dns.rrset.from_text(owner, ttl, IN, DNAME, newdom)) + rrsig = "DNAME 5 3 %d %s %s 12345 %s %s" % ( + ttl, + expiry, + inception, + domain, + sigdata, + ) + print("add RRSIG(DNAME) %s to %s" % (owner, newdom)) + sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig)) + owner = curname + "." + curdom + target = curname + "." + newdom + print("add synthesized CNAME %s to %s" % (owner, target)) + answers.append(dns.rrset.from_text(owner, ttl, IN, CNAME, target)) + rrsig = "CNAME 5 3 %d %s %s 12345 %s %s" % ( + ttl, + expiry, + inception, + domain, + sigdata, + ) + print("add synthesized RRSIG(CNAME) %s to %s" % (owner, target)) + sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig)) + curdom = newdom + continue + + # now add the final answer + owner = curname + "." + curdom + answers.append(dns.rrset.from_text(owner, ttl, IN, rrtype, final)) + rrsig = "%s 5 3 %d %s %s 12345 %s %s" % ( + typename, + ttl, + expiry, + inception, + domain, + sigdata, + ) + sigs.append(dns.rrset.from_text(owner, ttl, IN, RRSIG, rrsig)) + + # prepare the response and convert to wire format + r = dns.message.make_response(m) + + if name != "test": + r.answer.append(answers[-1]) + if wantsigs: + r.answer.append(sigs[-1]) + else: + for i, sig in rrs: + if sig and not wantsigs: + continue + elif sig: + r.answer.append(sigs[i]) + else: + r.answer.append(answers[i]) + + if typename != "NS": + r.authority.append( + dns.rrset.from_text(domain, ttl, IN, "NS", ("ns1.%s" % domain)) + ) + r.additional.append( + dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, A, additionalA) + ) + r.additional.append( + dns.rrset.from_text(("ns1.%s" % domain), 86400, IN, AAAA, additionalAAAA) + ) + + r.flags |= dns.flags.AA + r.use_edns() + return r.to_wire() + + +def sigterm(signum, frame): + print("Shutting down now...") + os.remove("ans.pid") + running = False + sys.exit(0) + + +############################################################################ +# Main +# +# Set up responder and control channel, open the pid file, and start +# the main loop, listening for queries on the query channel or commands +# on the control channel and acting on them. +############################################################################ +ip4 = "10.53.0.4" +ip6 = "fd92:7065:b8e:ffff::4" + +try: + port = int(os.environ["PORT"]) +except: + port = 5300 + +try: + ctrlport = int(os.environ["EXTRAPORT1"]) +except: + ctrlport = 5300 + +query4_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +query4_socket.bind((ip4, port)) + +havev6 = True +try: + query6_socket = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) + try: + query6_socket.bind((ip6, port)) + except: + query6_socket.close() + havev6 = False +except: + havev6 = False + +ctrl_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +ctrl_socket.bind((ip4, ctrlport)) +ctrl_socket.listen(5) + +signal.signal(signal.SIGTERM, sigterm) + +f = open("ans.pid", "w") +pid = os.getpid() +print(pid, file=f) +f.close() + +running = True + +print("Listening on %s port %d" % (ip4, port)) +if havev6: + print("Listening on %s port %d" % (ip6, port)) +print("Control channel on %s port %d" % (ip4, ctrlport)) +print("Ctrl-c to quit") + +if havev6: + input = [query4_socket, query6_socket, ctrl_socket] +else: + input = [query4_socket, ctrl_socket] + +while running: + try: + inputready, outputready, exceptready = select.select(input, [], []) + except select.error as e: + break + except socket.error as e: + break + except KeyboardInterrupt: + break + + for s in inputready: + if s == ctrl_socket: + # Handle control channel input + conn, addr = s.accept() + print("Control channel connected") + while True: + msg = conn.recv(65535) + if not msg: + break + ctl_channel(msg) + conn.close() + if s == query4_socket or s == query6_socket: + print("Query received on %s" % (ip4 if s == query4_socket else ip6)) + # Handle incoming queries + msg = s.recvfrom(65535) + rsp = create_response(msg[0]) + if rsp: + s.sendto(rsp, msg[1]) + if not running: + break diff --git a/bin/tests/system/chain/clean.sh b/bin/tests/system/chain/clean.sh new file mode 100755 index 0000000..57b05a7 --- /dev/null +++ b/bin/tests/system/chain/clean.sh @@ -0,0 +1,18 @@ +#!/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. + +rm -f dig.out.* named*.pid +rm -f ns*/named.conf +rm -f */named.memstats */named.recursing */named.lock */named.run */ans.run +rm -f ns2/K* ns2/dsset-* ns2/*.db.signed +rm -f ns*/managed-keys.bind* diff --git a/bin/tests/system/chain/ns1/named.conf.in b/bin/tests/system/chain/ns1/named.conf.in new file mode 100644 index 0000000..5504261 --- /dev/null +++ b/bin/tests/system/chain/ns1/named.conf.in @@ -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. + */ + +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 yes; + notify yes; +}; + +zone "." { type primary; file "root.db"; }; diff --git a/bin/tests/system/chain/ns1/root.db b/bin/tests/system/chain/ns1/root.db new file mode 100644 index 0000000..3469fb5 --- /dev/null +++ b/bin/tests/system/chain/ns1/root.db @@ -0,0 +1,51 @@ +; 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 root.domain.nil a.root.servers.nil. ( + 2016012800 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +example. NS ns2.example. +ns2.example. A 10.53.0.2 + +example.broken. NS ns3.example.broken. +ns3.example.broken. A 10.53.0.3 + +; for the resolver variant of the CVE-2021-25215 regression test +example.dname. NS ns3.example.dname. +ns3.example.dname. A 10.53.0.3 + +domain0.nil. NS ns2.domain0.nil +domain1.nil. NS ns2.domain0.nil +domain2.nil. NS ns2.domain0.nil +domain3.nil. NS ns2.domain0.nil +domain4.nil. NS ns2.domain0.nil +domain5.nil. NS ns2.domain0.nil +domain6.nil. NS ns2.domain0.nil +domain7.nil. NS ns2.domain0.nil +domain8.nil. NS ns2.domain0.nil +domain9.nil. NS ns2.domain0.nil +ns2.domain0.nil. A 10.53.0.2 +ns2.domain0.nil. AAAA fd92:7065:b8e:ffff::2 + +domain.nil. NS ns4.domain.nil +ns4.domain.nil. A 10.53.0.4 +ns4.domain.nil. AAAA fd92:7065:b8e:ffff::4 + +domain. NS ns4.domain. +ns4.domain. A 10.53.0.4 diff --git a/bin/tests/system/chain/ns2/example.db b/bin/tests/system/chain/ns2/example.db new file mode 100644 index 0000000..c13f2d2 --- /dev/null +++ b/bin/tests/system/chain/ns2/example.db @@ -0,0 +1,69 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2000042407 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns2 +ns2 A 10.53.0.2 + +a.short A 10.0.0.1 +short-dname DNAME short +a.longlonglonglonglonglonglonglonglonglonglonglonglong A 10.0.0.2 +long-dname DNAME longlonglonglonglonglonglonglonglonglonglonglonglong +toolong-dname DNAME longlonglonglonglonglonglonglonglonglonglonglonglong +cname CNAME a.cnamedname +cnamedname DNAME target +a.target A 10.0.0.3 + +; CNAME to delegation +; (unsigned delegations, external and internal) +sub5 NS ns5.sub5 +ns5.sub5 A 10.53.0.5 +a CNAME a.sub5 +sub2 NS ns2.sub2 +ns2.sub2 A 10.53.0.2 +b CNAME b.sub2 + +; (signed delegation, external and internal) +; note: these DS records are fake and will not validate; we're only +; testing that the resolver handles their presence in a reply correctly +signed-sub5 NS ns5.sub5 +signed-sub5 DS 44137 8 2 1CB4F54E0B4F4F85109143113A3C679716A2377D86EB0907846A03FB 0C0A3927 +c CNAME c.signed-sub5 +signed-sub2 NS ns2.sub2 +signed-sub2 DS 44137 8 2 1CB4F54E0B4F4F85109143113A3C679716A2377D86EB0907846A03FB 0C0A3927 +d CNAME d.signed-sub2 + +; long CNAME loop +loop CNAME goop +goop CNAME boop +boop CNAME soup +soup CNAME gump +gump CNAME bump +bump CNAME lump +lump CNAME rump +rump CNAME romp +romp CNAME bomp +bomp CNAME stomp +stomp CNAME clomp +clomp CNAME clump +clump CNAME hunk +hunk CNAME hank +hank CNAME bank +bank CNAME wank +wank CNAME woop +woop CNAME loop diff --git a/bin/tests/system/chain/ns2/generic.db b/bin/tests/system/chain/ns2/generic.db new file mode 100644 index 0000000..9d59378 --- /dev/null +++ b/bin/tests/system/chain/ns2/generic.db @@ -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. + +@ 86400 SOA ns2.domain0.nil. hostmaster.ns2.nil. 0 1 1 1 1 +@ 86400 NS ns2.domain0.nil. +ns2 86400 A 10.53.0.2 +ns2 86400 AAAA fd92:7065:b8e:ffff::2 + +@ 86400 A 1.2.3.4 +@ 86400 AAAA 1:2:3::4 +* 86400 A 1.2.3.4 +* 86400 AAAA 1:2:3::4 +; CVE-2021-25215 regression test data +self 86400 DNAME nil. diff --git a/bin/tests/system/chain/ns2/named.conf.in b/bin/tests/system/chain/ns2/named.conf.in new file mode 100644 index 0000000..922d2fa --- /dev/null +++ b/bin/tests/system/chain/ns2/named.conf.in @@ -0,0 +1,74 @@ +/* + * 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. + */ + +// NS2 + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion no; + dnssec-validation no; + notify yes; +}; + +zone "example" { + type primary; + file "example.db.signed"; + allow-update { any; }; +}; + +zone "sub2.example" { + type primary; + file "sub.db"; +}; + +zone "signed-sub2.example" { + type primary; + file "sub.db"; +}; + +zone "wildcard-secure.example" { + type primary; + file "wildcard-secure.example.db.signed"; +}; + +zone "wildcard-nsec.example" { + type primary; + file "wildcard-nsec.example.db.signed"; +}; + +zone "wildcard-nsec3.example" { + type primary; + file "wildcard-nsec3.example.db.signed"; +}; + +zone "wildcard-nsec3-optout.example" { + type primary; + file "wildcard-nsec3-optout.example.db.signed"; +}; + +zone "domain0.nil" { type primary; file "generic.db"; }; +zone "domain1.nil" { type primary; file "generic.db"; }; +zone "domain2.nil" { type primary; file "generic.db"; }; +zone "domain3.nil" { type primary; file "generic.db"; }; +zone "domain4.nil" { type primary; file "generic.db"; }; +zone "domain5.nil" { type primary; file "generic.db"; }; +zone "domain6.nil" { type primary; file "generic.db"; }; +zone "domain7.nil" { type primary; file "generic.db"; }; +zone "domain8.nil" { type primary; file "generic.db"; }; +zone "domain9.nil" { type primary; file "generic.db"; }; diff --git a/bin/tests/system/chain/ns2/sign.sh b/bin/tests/system/chain/ns2/sign.sh new file mode 100644 index 0000000..90d1912 --- /dev/null +++ b/bin/tests/system/chain/ns2/sign.sh @@ -0,0 +1,54 @@ +#!/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. + +. ../../conf.sh + +zone=example. +zonefile=example.db +signedfile=example.db.signed + +ksk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} -fk $zone) +zsk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} $zone) +$SIGNER -S -o $zone -f $signedfile $zonefile > /dev/null + +zone=wildcard-secure.example. +zonefile=wildcard-secure.db +signedfile=wildcard-secure.example.db.signed + +ksk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} -fk $zone) +zsk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} $zone) +$SIGNER -S -o $zone -f $signedfile $zonefile > /dev/null + +zone=wildcard-nsec.example. +zonefile=wildcard.db +signedfile=wildcard-nsec.example.db.signed + +ksk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} -fk $zone) +zsk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} $zone) +$SIGNER -S -o $zone -f $signedfile $zonefile > /dev/null + +zone=wildcard-nsec3.example. +zonefile=wildcard.db +signedfile=wildcard-nsec3.example.db.signed + +ksk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} -fk $zone) +zsk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} $zone) +$SIGNER -S -3 - -H 0 -o $zone -f $signedfile $zonefile > /dev/null + +zone=wildcard-nsec3-optout.example. +zonefile=wildcard.db +signedfile=wildcard-nsec3-optout.example.db.signed + +ksk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} -fk $zone) +zsk=$($KEYGEN -q -a ${DEFAULT_ALGORITHM} -b ${DEFAULT_BITS} $zone) +$SIGNER -S -3 - -H 0 -A -o $zone -f $signedfile $zonefile > /dev/null diff --git a/bin/tests/system/chain/ns2/sub.db b/bin/tests/system/chain/ns2/sub.db new file mode 100644 index 0000000..ad03165 --- /dev/null +++ b/bin/tests/system/chain/ns2/sub.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2017031001 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns2 +ns2 A 10.53.0.2 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 diff --git a/bin/tests/system/chain/ns2/wildcard-secure.db b/bin/tests/system/chain/ns2/wildcard-secure.db new file mode 100644 index 0000000..e39237a --- /dev/null +++ b/bin/tests/system/chain/ns2/wildcard-secure.db @@ -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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2021051901 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS localhost. + +delegation NS localhost. + DS 12345 13 2 0000000000000000000000000000000000000000000000000000000000000000 + +; CNAME pointing into a child zone +cname CNAME delegation + +; wildcard CNAME pointing at a CNAME pointing into a child zone +* CNAME cname diff --git a/bin/tests/system/chain/ns2/wildcard.db b/bin/tests/system/chain/ns2/wildcard.db new file mode 100644 index 0000000..cc39e9c --- /dev/null +++ b/bin/tests/system/chain/ns2/wildcard.db @@ -0,0 +1,28 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2021051901 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS localhost. + +delegation NS localhost. + +; CNAME pointing into a child zone +cname CNAME delegation + +; wildcard CNAME pointing at a CNAME pointing into a child zone +* CNAME cname diff --git a/bin/tests/system/chain/ns5/named.conf.in b/bin/tests/system/chain/ns5/named.conf.in new file mode 100644 index 0000000..86bbf26 --- /dev/null +++ b/bin/tests/system/chain/ns5/named.conf.in @@ -0,0 +1,42 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// NS2 + +options { + query-source address 10.53.0.5; + notify-source 10.53.0.5; + transfer-source 10.53.0.5; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.5; }; + listen-on-v6 { none; }; + recursion no; + dnssec-validation no; + notify yes; +}; + +zone "." { + type hint; + file "../../common/root.hint"; +}; + +zone "sub5.example" { + type primary; + file "sub.db"; +}; + +zone "signed-sub5.example" { + type primary; + file "sub.db"; +}; diff --git a/bin/tests/system/chain/ns5/sub.db b/bin/tests/system/chain/ns5/sub.db new file mode 100644 index 0000000..df571fb --- /dev/null +++ b/bin/tests/system/chain/ns5/sub.db @@ -0,0 +1,26 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, you can obtain one at https://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2017031001 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns5 +ns5 A 10.53.0.5 + +a A 10.0.0.1 +b A 10.0.0.2 +c A 10.0.0.3 +d A 10.0.0.4 diff --git a/bin/tests/system/chain/ns7/named.conf.in b/bin/tests/system/chain/ns7/named.conf.in new file mode 100644 index 0000000..32c9b5f --- /dev/null +++ b/bin/tests/system/chain/ns7/named.conf.in @@ -0,0 +1,45 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + directory "."; + query-source address 10.53.0.7; + notify-source 10.53.0.7; + transfer-source 10.53.0.7; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.7; }; + listen-on-v6 { fd92:7065:b8e:ffff::7; }; + recursion yes; + allow-recursion { any; }; + dnssec-validation yes; + deny-answer-aliases { + "example"; + } except-from { + "example"; + }; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm @DEFAULT_HMAC@; +}; + +controls { + inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +zone "." { + type hint; + file "root.hint"; +}; diff --git a/bin/tests/system/chain/ns7/root.hint b/bin/tests/system/chain/ns7/root.hint new file mode 100644 index 0000000..4f3f48b --- /dev/null +++ b/bin/tests/system/chain/ns7/root.hint @@ -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 diff --git a/bin/tests/system/chain/setup.sh b/bin/tests/system/chain/setup.sh new file mode 100644 index 0000000..6f52e65 --- /dev/null +++ b/bin/tests/system/chain/setup.sh @@ -0,0 +1,22 @@ +#!/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 + +copy_setports ns1/named.conf.in ns1/named.conf +copy_setports ns2/named.conf.in ns2/named.conf +copy_setports ns5/named.conf.in ns5/named.conf +copy_setports ns7/named.conf.in ns7/named.conf + +cd ns2 +$SHELL sign.sh diff --git a/bin/tests/system/chain/tests.sh b/bin/tests/system/chain/tests.sh new file mode 100644 index 0000000..3ad8e31 --- /dev/null +++ b/bin/tests/system/chain/tests.sh @@ -0,0 +1,630 @@ +#!/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. + +set -e + +. ../conf.sh + +DIGOPTS="-p ${PORT}" +RNDCCMD="$RNDC -c ../common/rndc.conf -p ${CONTROLPORT} -s" + +sendcmd() { + send 10.53.0.4 "${EXTRAPORT1}" +} + +status=0 +n=0 + +n=$((n + 1)) +echo_i "checking short DNAME from authoritative ($n)" +ret=0 +$DIG $DIGOPTS a.short-dname.example @10.53.0.2 a > dig.out.ns2.short || ret=1 +grep "status: NOERROR" dig.out.ns2.short > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking short DNAME from recursive ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS a.short-dname.example @10.53.0.7 a > dig.out.ns4.short || ret=1 +grep "status: NOERROR" dig.out.ns4.short > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking long DNAME from authoritative ($n)" +ret=0 +$DIG $DIGOPTS a.long-dname.example @10.53.0.2 a > dig.out.ns2.long || ret=1 +grep "status: NOERROR" dig.out.ns2.long > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking long DNAME from recursive ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS a.long-dname.example @10.53.0.7 a > dig.out.ns4.long || ret=1 +grep "status: NOERROR" dig.out.ns4.long > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking (too) long DNAME from authoritative ($n)" +ret=0 +$DIG $DIGOPTS 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.2 a > dig.out.ns2.toolong || ret=1 +grep "status: YXDOMAIN" dig.out.ns2.toolong > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking (too) long DNAME from recursive with cached DNAME ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.long-dname.example @10.53.0.7 a > dig.out.ns4.cachedtoolong || ret=1 +grep "status: YXDOMAIN" dig.out.ns4.cachedtoolong > /dev/null || ret=1 +grep '^long-dname\.example\..*DNAME.*long' dig.out.ns4.cachedtoolong > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking (too) long DNAME from recursive without cached DNAME ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS 01234567890123456789012345678901234567890123456789.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglonglong.longlonglonglonglonglonglonglonglonglonglonglonglonglong.toolong-dname.example @10.53.0.7 a > dig.out.ns4.uncachedtoolong || ret=1 +grep "status: YXDOMAIN" dig.out.ns4.uncachedtoolong > /dev/null || ret=1 +grep '^toolong-dname\.example\..*DNAME.*long' dig.out.ns4.uncachedtoolong > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +find_records() { + owner_name="$1" + rr_type="$2" + file="$3" + awk '$1 == "'"$owner_name"'" && $4 == "'"$rr_type"'" { print }' < "$file" +} + +count_records() { + owner_name="$1" + rr_type="$2" + file="$3" + find_records "$owner_name" "$rr_type" "$file" | wc -l +} + +exactly_one_record_exists_for() { + owner_name="$1" + rr_type="$2" + file="$3" + test "$(count_records "$owner_name" "$rr_type" "$file")" -eq 1 +} + +no_records_exist_for() { + owner_name="$1" + rr_type="$2" + file="$3" + test "$(count_records "$owner_name" "$rr_type" "$file")" -eq 0 +} + +ensure_no_ds_in_bitmap() { + owner_name="$1" + rr_type="$2" + file="$3" + case "$rr_type" in + NSEC) start_index=6 ;; + NSEC3) start_index=10 ;; + *) exit 1 ;; + esac + find_records "$owner_name" "$rr_type" "$file" | awk '{ for (i='"$start_index"'; i<=NF; i++) if ($i == "DS") exit 1 }' +} + +n=$((n + 1)) +echo_i "checking secure delegation prepared using CNAME chaining ($n)" +ret=0 +# QNAME exists, so the AUTHORITY section should only contain an NS RRset and a +# DS RRset. +$DIG $DIGOPTS @10.53.0.2 cname.wildcard-secure.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains the expected NS and DS RRsets. +exactly_one_record_exists_for "delegation.wildcard-secure.example." NS dig.out.2.$n || ret=1 +exactly_one_record_exists_for "delegation.wildcard-secure.example." DS dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking secure delegation prepared using wildcard expansion + CNAME chaining ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset, an +# NSEC record proving nonexistence of QNAME, and a DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 a-nonexistent-name.wildcard-secure.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains the expected NS and DS RRsets. +exactly_one_record_exists_for "delegation.wildcard-secure.example." NS dig.out.2.$n || ret=1 +exactly_one_record_exists_for "delegation.wildcard-secure.example." DS dig.out.2.$n || ret=1 +# Check NSEC records in the AUTHORITY section. +no_records_exist_for "wildcard-secure.example." NSEC dig.out.2.$n || ret=1 +exactly_one_record_exists_for "*.wildcard-secure.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "cname.wildcard-secure.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-secure.example." NSEC dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using CNAME chaining, NSEC ($n)" +ret=0 +# QNAME exists, so the AUTHORITY section should only contain an NS RRset and a +# single NSEC record proving nonexistence of a DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 cname.wildcard-nsec.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec.example." DS dig.out.2.$n || ret=1 +# Check NSEC records in the AUTHORITY section. +no_records_exist_for "wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "*.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "cname.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +# Ensure the NSEC record for the zone cut does not have the DS bit set in the +# type bit map. +ensure_no_ds_in_bitmap "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC, QNAME #1 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, these two NSEC records are different. +$DIG $DIGOPTS @10.53.0.2 a-nonexistent-name.wildcard-nsec.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec.example." DS dig.out.2.$n || ret=1 +# Check NSEC records in the AUTHORITY section. +no_records_exist_for "wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +exactly_one_record_exists_for "*.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "cname.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +# Ensure the NSEC record for the zone cut does not have the DS bit set in the +# type bit map. +ensure_no_ds_in_bitmap "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC, QNAME #2 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, the same NSEC record proves nonexistence of both the +# QNAME and the DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 z-nonexistent-name.wildcard-nsec.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec.example." DS dig.out.2.$n || ret=1 +# Check NSEC records in the AUTHORITY section. +no_records_exist_for "wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "*.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +no_records_exist_for "cname.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +exactly_one_record_exists_for "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +# Ensure the NSEC record for the zone cut does not have the DS bit set in the +# type bit map. +ensure_no_ds_in_bitmap "delegation.wildcard-nsec.example." NSEC dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# Relevant NSEC3 hashes: +# +# - existing names: +# +# $ nsec3hash - 1 0 wildcard-nsec3.example. +# 38IVP9CN0LBISO6H3V5REQCKMTHLI5AN (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 cname.wildcard-nsec3.example. +# 3DV6GNNVR0O8LA4DC4CHL2JTVNHT8Q1D (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 delegation.wildcard-nsec3.example. +# AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 *.wildcard-nsec3.example. +# Q64D8L8HLSB3L98S59PM8OSSMI7SMQA2 (salt=-, hash=1, iterations=0) +# +# - nonexistent names: +# +# $ nsec3hash - 1 0 a-nonexistent-name.wildcard-nsec3.example. +# PST9IH6M0DG3M139CO3G12NUP4ER88SH (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 z-nonexistent-name.wildcard-nsec3.example. +# SG2DEHEAOGCKP7FTNQAUVC3I3TIPJH0J (salt=-, hash=1, iterations=0) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using CNAME chaining, NSEC3 ($n)" +ret=0 +# QNAME exists, so the AUTHORITY section should only contain an NS RRset and a +# single NSEC3 record proving nonexistence of a DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 cname.wildcard-nsec3.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +no_records_exist_for "38IVP9CN0LBISO6H3V5REQCKMTHLI5AN.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "3DV6GNNVR0O8LA4DC4CHL2JTVNHT8Q1D.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "Q64D8L8HLSB3L98S59PM8OSSMI7SMQA2.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record matching the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC3, QNAME #1 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC3 records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, these two NSEC3 records are different. +$DIG $DIGOPTS @10.53.0.2 z-nonexistent-name.wildcard-nsec3.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +no_records_exist_for "38IVP9CN0LBISO6H3V5REQCKMTHLI5AN.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "3DV6GNNVR0O8LA4DC4CHL2JTVNHT8Q1D.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "Q64D8L8HLSB3L98S59PM8OSSMI7SMQA2.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record matching the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC3, QNAME #2 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC3 records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, the same NSEC3 record proves nonexistence of both the +# QNAME and the DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 a-nonexistent-name.wildcard-nsec3.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +no_records_exist_for "38IVP9CN0LBISO6H3V5REQCKMTHLI5AN.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "3DV6GNNVR0O8LA4DC4CHL2JTVNHT8Q1D.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "Q64D8L8HLSB3L98S59PM8OSSMI7SMQA2.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record matching the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "AVKOGGGVJHFSLQA68TILKFKJ94AV4MNC.wildcard-nsec3.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# Relevant NSEC3 hashes: +# +# - existing names with corresponding NSEC3 records: +# +# $ nsec3hash - 1 0 *.wildcard-nsec3-optout.example. +# 2JGSPT59VJ7R9SQB5B9P6HPM5JBATOOO (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 cname.wildcard-nsec3-optout.example. +# OKRFKC9SS1O60E8U2980UD62MUSMKGUG (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 wildcard-nsec3-optout.example. +# SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI (salt=-, hash=1, iterations=0) +# +# - existing name with no corresponding NSEC3 record due to opt-out: +# +# $ nsec3hash - 1 0 delegation.wildcard-nsec3-optout.example. +# UFP8PVECFTD57HU5PUD2HE0ES37QEOAP (salt=-, hash=1, iterations=0) +# +# - nonexistent names: +# +# $ nsec3hash - 1 0 b-nonexistent-name.wildcard-nsec3-optout.example. +# 3J38JE2OU0O7B4CE2ADMBBKJ5HT994S5 (salt=-, hash=1, iterations=0) +# $ nsec3hash - 1 0 z-nonexistent-name.wildcard-nsec3-optout.example. +# V7OTS4791T9SU0HKVL93EVNAJ9JH2CH3 (salt=-, hash=1, iterations=0) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using CNAME chaining, NSEC3 with opt-out ($n)" +ret=0 +# QNAME exists, so the AUTHORITY section should only contain an NS RRset and a +# single NSEC3 record proving nonexistence of a DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 cname.wildcard-nsec3-optout.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3-optout.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3-optout.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +no_records_exist_for "2JGSPT59VJ7R9SQB5B9P6HPM5JBATOOO.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "OKRFKC9SS1O60E8U2980UD62MUSMKGUG.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record covering the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC3 with opt-out, QNAME #1 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC3 records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, these two NSEC3 records are different. +$DIG $DIGOPTS @10.53.0.2 b-nonexistent-name.wildcard-nsec3-optout.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3-optout.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3-optout.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +exactly_one_record_exists_for "2JGSPT59VJ7R9SQB5B9P6HPM5JBATOOO.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "OKRFKC9SS1O60E8U2980UD62MUSMKGUG.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record covering the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking insecure delegation prepared using wildcard expansion + CNAME chaining, NSEC3 with opt-out, QNAME #2 ($n)" +ret=0 +# QNAME does not exist, so the AUTHORITY section should contain an NS RRset and +# NSEC3 records proving nonexistence of both QNAME and a DS RRset at the zone +# cut. In this test case, the same NSEC3 record proves nonexistence of both the +# QNAME and the DS RRset at the zone cut. +$DIG $DIGOPTS @10.53.0.2 z-nonexistent-name.wildcard-nsec3-optout.example A +norec +dnssec > dig.out.2.$n 2>&1 || ret=1 +# Ensure that the AUTHORITY section contains an NS RRset without an associated +# DS RRset. +exactly_one_record_exists_for "delegation.wildcard-nsec3-optout.example." NS dig.out.2.$n || ret=1 +no_records_exist_for "delegation.wildcard-nsec3-optout.example." DS dig.out.2.$n || ret=1 +# Check NSEC3 records in the AUTHORITY section. +no_records_exist_for "2JGSPT59VJ7R9SQB5B9P6HPM5JBATOOO.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +no_records_exist_for "OKRFKC9SS1O60E8U2980UD62MUSMKGUG.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +exactly_one_record_exists_for "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +# Ensure the NSEC3 record covering the zone cut does not have the DS bit set in +# the type bit map. +ensure_no_ds_in_bitmap "SS5M1RUBSGMANEQ1VLRDDEC6SOAT7HNI.wildcard-nsec3-optout.example." NSEC3 dig.out.2.$n || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to DNAME from authoritative ($n)" +ret=0 +$DIG $DIGOPTS cname.example @10.53.0.2 a > dig.out.ns2.cname +grep "status: NOERROR" dig.out.ns2.cname > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to DNAME from recursive" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS cname.example @10.53.0.7 a > dig.out.ns4.cname +grep "status: NOERROR" dig.out.ns4.cname > /dev/null || ret=1 +grep '^cname.example.' dig.out.ns4.cname > /dev/null || ret=1 +grep '^cnamedname.example.' dig.out.ns4.cname > /dev/null || ret=1 +grep '^a.cnamedname.example.' dig.out.ns4.cname > /dev/null || ret=1 +grep '^a.target.example.' dig.out.ns4.cname > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DNAME is returned with synthesized CNAME before DNAME ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 name.synth-then-dname.example.broken A > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep '^name.synth-then-dname\.example\.broken\..*CNAME.*name.$' dig.out.test$n > /dev/null || ret=1 +grep '^synth-then-dname\.example\.broken\..*DNAME.*\.$' dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DNAME is returned with CNAME to synthesized CNAME before DNAME ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 cname-to-synth2-then-dname.example.broken A > dig.out.test$n +grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1 +grep '^cname-to-synth2-then-dname\.example\.broken\..*CNAME.*name\.synth2-then-dname\.example\.broken.$' dig.out.test$n > /dev/null || ret=1 +grep '^name\.synth2-then-dname\.example\.broken\..*CNAME.*name.$' dig.out.test$n > /dev/null || ret=1 +grep '^synth2-then-dname\.example\.broken\..*DNAME.*\.$' dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME loops are detected ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 loop.example > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 17" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to external delegated zones is handled ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 a.example > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 2" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to internal delegated zones is handled ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 b.example > dig.out.test$n +grep "status: NOERROR" dig.out.test$n > /dev/null || ret=1 +grep "ANSWER: 2" dig.out.test$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to signed external delegation is handled ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 c.example > dig.out.$n +grep "status: NOERROR" dig.out.$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME to signed internal delegation is handled ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 d.example > dig.out.$n +grep "status: NOERROR" dig.out.$n > /dev/null || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking CNAME chains in various orders ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n - step 1 --- 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|1,2,3,4,s1,s2,s3,s4" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.1.$n 2>&1 +grep 'status: NOERROR' dig.out.1.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.1.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 2 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|1,1,2,2,3,4,s4,s3,s1" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.2.$n 2>&1 +grep 'status: NOERROR' dig.out.2.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.2.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 3 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|2,1,3,4,s3,s1,s2,s4" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.3.$n 2>&1 +grep 'status: NOERROR' dig.out.3.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.3.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 4 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|4,3,2,1,s4,s3,s2,s1" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.4.$n 2>&1 +grep 'status: NOERROR' dig.out.4.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.4.$n > /dev/null 2>&1 || ret=1 +echo "cname,cname,cname|4,3,2,1,s4,s3,s2,s1" | sendcmd +$RNDCCMD 10.53.0.7 null --- start test$n - step 5 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.5.$n 2>&1 +grep 'status: NOERROR' dig.out.5.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.5.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 6 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|4,3,3,3,s1,s1,1,3,4" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.6.$n 2>&1 +grep 'status: NOERROR' dig.out.6.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.6.$n > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking that only the initial CNAME is cached ($n)" +ret=0 +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "cname,cname,cname|1,2,3,4,s1,s2,s3,s4" | sendcmd +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.1.$n 2>&1 +sleep 1 +$DIG $DIGOPTS +noall +answer @10.53.0.7 cname1.domain.nil > dig.out.2.$n 2>&1 +ttl=$(awk '{print $2}' dig.out.2.$n) +[ "$ttl" -eq 86400 ] || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DNAME chains in various orders ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n - step 1 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "dname,dname|5,4,3,2,1,s5,s4,s3,s2,s1" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.1.$n 2>&1 +grep 'status: NOERROR' dig.out.1.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 3' dig.out.1.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 2 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "dname,dname|5,4,3,2,1,s5,s4,s3,s2,s1" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.2.$n 2>&1 +grep 'status: NOERROR' dig.out.2.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 3' dig.out.2.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 3 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "dname,dname|2,3,s1,s2,s3,s4,1" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.3.$n 2>&1 +grep 'status: NOERROR' dig.out.3.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 3' dig.out.3.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking external CNAME/DNAME chains in various orders ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n - step 1 --- 2>&1 | sed 's/^/ns7 /' | cat_i +echo "xname,dname|1,2,3,4,s1,s2,s3,s4" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.1.$n 2>&1 +grep 'status: NOERROR' dig.out.1.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.1.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 2 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "xname,dname|s2,2,s1,1,4,s4,3" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.2.$n 2>&1 +grep 'status: NOERROR' dig.out.2.$n > /dev/null 2>&1 || ret=1 +grep 'ANSWER: 2' dig.out.2.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 null --- start test$n - step 3 --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +echo "xname,dname|s2,2,2,2" | sendcmd +$DIG $DIGOPTS @10.53.0.7 test.domain.nil > dig.out.3.$n 2>&1 +grep 'status: SERVFAIL' dig.out.3.$n > /dev/null 2>&1 || ret=1 +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking explicit DNAME query ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 dname short-dname.example > dig.out.7.$n 2>&1 +grep 'status: NOERROR' dig.out.7.$n > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +n=$((n + 1)) +echo_i "checking DNAME via ANY query ($n)" +ret=0 +$RNDCCMD 10.53.0.7 null --- start test$n --- 2>&1 | sed 's/^/ns7 /' | cat_i +$RNDCCMD 10.53.0.7 flush 2>&1 | sed 's/^/ns7 /' | cat_i +$DIG $DIGOPTS @10.53.0.7 any short-dname.example > dig.out.7.$n 2>&1 +grep 'status: NOERROR' dig.out.7.$n > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# Regression test for CVE-2021-25215 (authoritative server). +n=$((n + 1)) +echo_i "checking DNAME resolution via itself (authoritative) ($n)" +ret=0 +$DIG $DIGOPTS @10.53.0.2 DNAME self.domain0.self.domain0.nil. > dig.out.2.$n 2>&1 +grep 'status: NOERROR' dig.out.2.$n > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +# Regression test for CVE-2021-25215 (recursive resolver). +n=$((n + 1)) +echo_i "checking DNAME resolution via itself (recursive) ($n)" +ret=0 +$DIG $DIGOPTS @10.53.0.7 DNAME self.example.self.example.dname. > dig.out.7.$n 2>&1 +grep 'status: NOERROR' dig.out.7.$n > /dev/null 2>&1 || ret=1 +if [ $ret != 0 ]; then echo_i "failed"; fi +status=$((status + ret)) + +echo_i "exit status: $status" +[ $status -eq 0 ] || exit 1 diff --git a/bin/tests/system/chain/tests_sh_chain.py b/bin/tests/system/chain/tests_sh_chain.py new file mode 100644 index 0000000..ca3c057 --- /dev/null +++ b/bin/tests/system/chain/tests_sh_chain.py @@ -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. + + +def test_chain(run_tests_sh): + run_tests_sh() |