summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/chain
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 18:37:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-05 18:37:14 +0000
commitea648e70a989cca190cd7403fe892fd2dcc290b4 (patch)
treee2b6b1c647da68b0d4d66082835e256eb30970e8 /bin/tests/system/chain
parentInitial commit. (diff)
downloadbind9-ea648e70a989cca190cd7403fe892fd2dcc290b4.tar.xz
bind9-ea648e70a989cca190cd7403fe892fd2dcc290b4.zip
Adding upstream version 1:9.11.5.P4+dfsg.upstream/1%9.11.5.P4+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bin/tests/system/chain')
-rw-r--r--bin/tests/system/chain/README15
-rw-r--r--bin/tests/system/chain/ans3/ans.pl101
-rw-r--r--bin/tests/system/chain/ans4/README.anspy17
-rwxr-xr-xbin/tests/system/chain/ans4/ans.py347
-rwxr-xr-xbin/tests/system/chain/clean.sh15
-rw-r--r--bin/tests/system/chain/ns1/named.conf.in25
-rw-r--r--bin/tests/system/chain/ns1/root.db45
-rw-r--r--bin/tests/system/chain/ns2/example.db67
-rw-r--r--bin/tests/system/chain/ns2/generic.db17
-rw-r--r--bin/tests/system/chain/ns2/named.conf.in51
-rw-r--r--bin/tests/system/chain/ns2/sign.sh20
-rw-r--r--bin/tests/system/chain/ns2/sub.db24
-rw-r--r--bin/tests/system/chain/ns5/named.conf.in39
-rw-r--r--bin/tests/system/chain/ns5/sub.db24
-rw-r--r--bin/tests/system/chain/ns7/named.conf.in43
-rw-r--r--bin/tests/system/chain/ns7/root.hint12
-rw-r--r--bin/tests/system/chain/prereq.sh50
-rw-r--r--bin/tests/system/chain/setup.sh25
-rw-r--r--bin/tests/system/chain/tests.sh269
19 files changed, 1206 insertions, 0 deletions
diff --git a/bin/tests/system/chain/README b/bin/tests/system/chain/README
new file mode 100644
index 0000000..f51c123
--- /dev/null
+++ b/bin/tests/system/chain/README
@@ -0,0 +1,15 @@
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
+
+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..cdbfc84
--- /dev/null
+++ b/bin/tests/system/chain/ans3/ans.pl
@@ -0,0 +1,101 @@
+#!/usr/bin/env perl
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://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";
+ } 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..a419c61
--- /dev/null
+++ b/bin/tests/system/chain/ans4/README.anspy
@@ -0,0 +1,17 @@
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
+
+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..2dd7def
--- /dev/null
+++ b/bin/tests/system/chain/ans4/ans.py
@@ -0,0 +1,347 @@
+############################################################################
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://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 origninal
+# 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..265123f
--- /dev/null
+++ b/bin/tests/system/chain/clean.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://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/example.db.signed
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..5815c3c
--- /dev/null
+++ b/bin/tests/system/chain/ns1/named.conf.in
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 http://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ notify yes;
+ dnssec-enable yes;
+};
+
+zone "." { type master; 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..bc70f8c
--- /dev/null
+++ b/bin/tests/system/chain/ns1/root.db
@@ -0,0 +1,45 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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
+
+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..5f29f86
--- /dev/null
+++ b/bin/tests/system/chain/ns2/example.db
@@ -0,0 +1,67 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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..60568dd
--- /dev/null
+++ b/bin/tests/system/chain/ns2/generic.db
@@ -0,0 +1,17 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+@ 86400 SOA ns2.nil. hostmaster.ns2.nil. 0 1 1 1 1
+@ 86400 NS ns2.nil.
+ns2 86400 A 10.53.0.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
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..519bfb3
--- /dev/null
+++ b/bin/tests/system/chain/ns2/named.conf.in
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 http://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;
+ notify yes;
+};
+
+zone "example" {
+ type master;
+ file "example.db.signed";
+ allow-update { any; };
+};
+
+zone "sub2.example" {
+ type master;
+ file "sub.db";
+};
+
+zone "signed-sub2.example" {
+ type master;
+ file "sub.db";
+};
+
+zone "domain0.nil" { type master; file "generic.db"; };
+zone "domain1.nil" { type master; file "generic.db"; };
+zone "domain2.nil" { type master; file "generic.db"; };
+zone "domain3.nil" { type master; file "generic.db"; };
+zone "domain4.nil" { type master; file "generic.db"; };
+zone "domain5.nil" { type master; file "generic.db"; };
+zone "domain6.nil" { type master; file "generic.db"; };
+zone "domain7.nil" { type master; file "generic.db"; };
+zone "domain8.nil" { type master; file "generic.db"; };
+zone "domain9.nil" { type master; 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..11b87ad
--- /dev/null
+++ b/bin/tests/system/chain/ns2/sign.sh
@@ -0,0 +1,20 @@
+#!/bin/sh -e
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+SYSTEMTESTTOP=../..
+. $SYSTEMTESTTOP/conf.sh
+
+zone=example.
+zonefile=example.db
+
+ksk=`$KEYGEN -q -a RSASHA256 -b 2048 -fk -r $RANDFILE $zone`
+zsk=`$KEYGEN -q -a RSASHA256 -b 1024 -r $RANDFILE $zone`
+$SIGNER -S -r $RANDFILE -o $zone example.db > /dev/null 2>&1
diff --git a/bin/tests/system/chain/ns2/sub.db b/bin/tests/system/chain/ns2/sub.db
new file mode 100644
index 0000000..5e65fdf
--- /dev/null
+++ b/bin/tests/system/chain/ns2/sub.db
@@ -0,0 +1,24 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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/ns5/named.conf.in b/bin/tests/system/chain/ns5/named.conf.in
new file mode 100644
index 0000000..d6813b6
--- /dev/null
+++ b/bin/tests/system/chain/ns5/named.conf.in
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 http://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;
+ notify yes;
+};
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
+
+zone "sub5.example" {
+ type master;
+ file "sub.db";
+};
+
+zone "signed-sub5.example" {
+ type master;
+ 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..9ddb431
--- /dev/null
+++ b/bin/tests/system/chain/ns5/sub.db
@@ -0,0 +1,24 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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..c314922
--- /dev/null
+++ b/bin/tests/system/chain/ns7/named.conf.in
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * 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 http://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 hmac-sha256;
+};
+
+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..ab755ba
--- /dev/null
+++ b/bin/tests/system/chain/ns7/root.hint
@@ -0,0 +1,12 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; 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 http://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/prereq.sh b/bin/tests/system/chain/prereq.sh
new file mode 100644
index 0000000..f3f1939
--- /dev/null
+++ b/bin/tests/system/chain/prereq.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+$SHELL ../testcrypto.sh || exit 255
+
+if test -n "$PYTHON"
+then
+ if $PYTHON -c "import dns" 2> /dev/null
+ then
+ :
+ else
+ echo_i "This test requires the dnspython module." >&2
+ exit 1
+ fi
+else
+ echo_i "This test requires Python and the dnspython module." >&2
+ exit 1
+fi
+
+if $PERL -e 'use Net::DNS;' 2>/dev/null
+then
+ if $PERL -e 'use Net::DNS; die if ($Net::DNS::VERSION >= 0.69 && $Net::DNS::VERSION <= 0.74);' 2>/dev/null
+ then
+ :
+ else
+ echo_i "Net::DNS versions 0.69 to 0.74 have bugs that cause this test to fail: please update." >&2
+ exit 1
+ fi
+else
+ echo_i "This test requires the perl Net::DNS library." >&2
+ exit 1
+fi
+if $PERL -e 'use Net::DNS::Nameserver;' 2>/dev/null
+then
+ :
+else
+ echo_i "This test requires the Net::DNS::Nameserver library." >&2
+ exit 1
+fi
diff --git a/bin/tests/system/chain/setup.sh b/bin/tests/system/chain/setup.sh
new file mode 100644
index 0000000..c2b0d69
--- /dev/null
+++ b/bin/tests/system/chain/setup.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+#
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+$SHELL clean.sh
+
+test -r $RANDFILE || $GENRANDOM 400 $RANDFILE
+
+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..e7ad91e
--- /dev/null
+++ b/bin/tests/system/chain/tests.sh
@@ -0,0 +1,269 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# 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 http://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+DIGOPTS="-p ${PORT}"
+RNDCCMD="$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p ${CONTROLPORT} -s"
+SEND="$PERL $SYSTEMTESTTOP/send.pl 10.53.0.4 ${EXTRAPORT1}"
+status=0
+n=0
+
+n=`expr $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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking short DNAME from recursive ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking long DNAME from recursive ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking (too) long DNAME from recursive with cached DNAME ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking (too) long DNAME from recursive without cached DNAME ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME to DNAME from recursive"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking DNAME is returned with synthesized CNAME before DNAME ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking DNAME is returned with CNAME to synthesized CNAME before DNAME ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME loops are detected ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME to external delegated zones is handled ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME to internal delegated zones is handled ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME to signed external delgation is handled ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME to signed internal delgation is handled ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking CNAME chains in various orders ($n)"
+ret=0
+echo "cname,cname,cname|1,2,3,4,s1,s2,s3,s4" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "cname,cname,cname|1,1,2,2,3,4,s4,s3,s1" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "cname,cname,cname|2,1,3,4,s3,s1,s2,s4" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "cname,cname,cname|4,3,2,1,s4,s3,s2,s1" | $SEND
+$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" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "cname,cname,cname|4,3,3,3,s1,s1,1,3,4" | $SEND
+$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=`expr $status + $ret`
+
+n=`expr $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" | $SEND
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking DNAME chains in various orders ($n)"
+ret=0
+$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" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "dname,dname|5,4,3,2,1,s5,s4,s3,s2,s1" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "dname,dname|2,3,s1,s2,s3,s4,1" | $SEND
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking external CNAME/DNAME chains in various orders ($n)"
+ret=0
+echo "xname,dname|1,2,3,4,s1,s2,s3,s4" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "xname,dname|s2,2,s1,1,4,s4,3" | $SEND
+$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 flush 2>&1 | sed 's/^/ns7 /' | cat_i
+echo "xname,dname|s2,2,2,2" | $SEND
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking explicit DNAME query ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "checking DNAME via ANY query ($n)"
+ret=0
+$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=`expr $status + $ret`
+
+echo_i "exit status: $status"
+[ $status -eq 0 ] || exit 1