summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/reclimit
diff options
context:
space:
mode:
Diffstat (limited to 'bin/tests/system/reclimit')
-rw-r--r--bin/tests/system/reclimit/README13
-rw-r--r--bin/tests/system/reclimit/ans2/ans.pl233
-rw-r--r--bin/tests/system/reclimit/ans7/ans.pl74
-rw-r--r--bin/tests/system/reclimit/clean.sh18
-rw-r--r--bin/tests/system/reclimit/ns1/named.conf.in24
-rw-r--r--bin/tests/system/reclimit/ns1/root.db17
-rw-r--r--bin/tests/system/reclimit/ns3/hints.db11
-rw-r--r--bin/tests/system/reclimit/ns3/named1.conf.in34
-rw-r--r--bin/tests/system/reclimit/ns3/named2.conf.in34
-rw-r--r--bin/tests/system/reclimit/ns3/named3.conf.in35
-rw-r--r--bin/tests/system/reclimit/ns3/named4.conf.in35
-rw-r--r--bin/tests/system/reclimit/prereq.sh35
-rw-r--r--bin/tests/system/reclimit/setup.sh17
-rw-r--r--bin/tests/system/reclimit/tests.sh191
14 files changed, 771 insertions, 0 deletions
diff --git a/bin/tests/system/reclimit/README b/bin/tests/system/reclimit/README
new file mode 100644
index 0000000..bb7f596
--- /dev/null
+++ b/bin/tests/system/reclimit/README
@@ -0,0 +1,13 @@
+Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+
+See COPYRIGHT in the source root or http://isc.org/copyright.html for terms.
+
+system test for recursion limits
+
+ns1 -- root server
+ans2 -- for example.org: delegate to ns1.(n+1).example.org for all n, up to the
+ value specified in ans.limit (or forever if limit is 0)
+ for example.net: delegate every query to 15 more name servers, with
+ "victim" address
+ns3 -- resolver under test
+ans7 -- "victim" server
diff --git a/bin/tests/system/reclimit/ans2/ans.pl b/bin/tests/system/reclimit/ans2/ans.pl
new file mode 100644
index 0000000..988445d
--- /dev/null
+++ b/bin/tests/system/reclimit/ans2/ans.pl
@@ -0,0 +1,233 @@
+#!/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 IO::Socket;
+use Net::DNS;
+
+my $localaddr = "10.53.0.2";
+my $limit = getlimit();
+my $no_more_waiting = 0;
+my @delayed_response;
+my $timeout;
+
+my $localport = int($ENV{'PORT'});
+if (!$localport) { $localport = 5300; }
+
+my $udpsock = IO::Socket::INET->new(LocalAddr => "$localaddr",
+ LocalPort => $localport, Proto => "udp", Reuse => 1) or die "$!";
+
+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 $count = 0;
+my $send_response = 0;
+
+sub getlimit {
+ if ( -e "ans.limit") {
+ open(FH, "<", "ans.limit");
+ my $line = <FH>;
+ chomp $line;
+ close FH;
+ if ($line =~ /^\d+$/) {
+ return $line;
+ }
+ }
+
+ return 0;
+}
+
+# If $wait == 0 is returned, returned reply will be sent immediately.
+# If $wait == 1 is returned, sending the returned reply might be delayed; see
+# comments inside handle_UDP() for details.
+sub reply_handler {
+ my ($qname, $qclass, $qtype) = @_;
+ my ($rcode, @ans, @auth, @add, $wait);
+
+ print ("request: $qname/$qtype\n");
+ STDOUT->flush();
+
+ $wait = 0;
+ $count += 1;
+
+ if ($qname eq "count" ) {
+ if ($qtype eq "TXT") {
+ my ($ttl, $rdata) = (0, "$count");
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ push @ans, $rr;
+ print ("\tcount: $count\n");
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "reset" ) {
+ $count = 0;
+ $send_response = 0;
+ $limit = getlimit();
+ $rcode = "NOERROR";
+ print ("\tlimit: $limit\n");
+ } elsif ($qname eq "direct.example.org" ) {
+ if ($qtype eq "A") {
+ my ($ttl, $rdata) = (3600, $localaddr);
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "indirect1.example.org" ||
+ $qname eq "indirect2.example.org" ||
+ $qname eq "indirect3.example.org" ||
+ $qname eq "indirect4.example.org" ||
+ $qname eq "indirect5.example.org" ||
+ $qname eq "indirect6.example.org" ||
+ $qname eq "indirect7.example.org" ||
+ $qname eq "indirect8.example.org") {
+ if (! $send_response) {
+ my $rr = new Net::DNS::RR("$qname 86400 $qclass NS ns1.1.example.org");
+ push @auth, $rr;
+ } elsif ($qtype eq "A") {
+ my ($ttl, $rdata) = (3600, $localaddr);
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname =~ /^ns1\.(\d+)\.example\.org$/) {
+ my $next = $1 + 1;
+ $wait = 1;
+ if ($limit == 0 || (! $send_response && $next <= $limit)) {
+ my $rr = new Net::DNS::RR("$1.example.org 86400 $qclass NS ns1.$next.example.org");
+ push @auth, $rr;
+ } else {
+ $send_response = 1;
+ if ($qtype eq "A") {
+ my ($ttl, $rdata) = (3600, $localaddr);
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ print("\tresponse: $qname $ttl $qclass $qtype $rdata\n");
+ push @ans, $rr;
+ }
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "direct.example.net" ) {
+ if ($qtype eq "A") {
+ my ($ttl, $rdata) = (3600, $localaddr);
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ push @ans, $rr;
+ }
+ $rcode = "NOERROR";
+ } elsif( $qname =~ /^ns1\.(\d+)\.example\.net$/ ) {
+ my $next = ($1 + 1) * 16;
+ for (my $i = 1; $i < 16; $i++) {
+ my $s = $next + $i;
+ my $rr = new Net::DNS::RR("$1.example.net 86400 $qclass NS ns1.$s.example.net");
+ push @auth, $rr;
+ $rr = new Net::DNS::RR("ns1.$s.example.net 86400 $qclass A 10.53.0.7");
+ push @add, $rr;
+ }
+ $rcode = "NOERROR";
+ } else {
+ $rcode = "NXDOMAIN";
+ }
+
+ return ($rcode, \@ans, \@auth, \@add, $wait);
+}
+
+sub handleUDP {
+ my ($buf, $peer) = @_;
+ my ($request, $rcode, $ans, $auth, $add, $wait);
+
+ $request = new Net::DNS::Packet(\$buf, 0);
+ $@ and die $@;
+
+ my ($question) = $request->question;
+ my $qname = $question->qname;
+ my $qclass = $question->qclass;
+ my $qtype = $question->qtype;
+
+ ($rcode, $ans, $auth, $add, $wait) = reply_handler($qname, $qclass, $qtype);
+
+ my $reply = $request->reply();
+
+ $reply->header->rcode($rcode);
+ $reply->header->aa(@$ans ? 1 : 0);
+ $reply->header->id($request->header->id);
+ $reply->{answer} = $ans if $ans;
+ $reply->{authority} = $auth if $auth;
+ $reply->{additional} = $add if $add;
+
+ if ($wait) {
+ # reply_handler() asked us to delay sending this reply until
+ # another reply with $wait == 1 is generated or a timeout
+ # occurs.
+ if (@delayed_response) {
+ # A delayed reply is already queued, so we can now send
+ # both the delayed reply and the current reply.
+ send_delayed_response();
+ return $reply;
+ } elsif ($no_more_waiting) {
+ # It was determined before that there is no point in
+ # waiting for "accompanying" queries. Thus, send the
+ # current reply immediately.
+ return $reply;
+ } else {
+ # No delayed reply is queued and the client is expected
+ # to send an "accompanying" query shortly. Do not send
+ # the current reply right now, just save it for later
+ # and wait for an "accompanying" query to be received.
+ @delayed_response = ($reply, $peer);
+ $timeout = 0.5;
+ return;
+ }
+ } else {
+ # Send reply immediately.
+ return $reply;
+ }
+}
+
+sub send_delayed_response {
+ my ($reply, $peer) = @delayed_response;
+ # Truncation to 512 bytes is required for triggering "NS explosion" on
+ # builds without IPv6 support
+ $udpsock->send($reply->data(512), 0, $peer);
+ undef @delayed_response;
+ undef $timeout;
+}
+
+# Main
+my $rin;
+my $rout;
+for (;;) {
+ $rin = '';
+ vec($rin, fileno($udpsock), 1) = 1;
+
+ select($rout = $rin, undef, undef, $timeout);
+
+ if (vec($rout, fileno($udpsock), 1)) {
+ my ($buf, $peer, $reply);
+ $udpsock->recv($buf, 512);
+ $peer = $udpsock->peername();
+ $reply = handleUDP($buf, $peer);
+ # Truncation to 512 bytes is required for triggering "NS
+ # explosion" on builds without IPv6 support
+ $udpsock->send($reply->data(512), 0, $peer) if $reply;
+ } else {
+ # An "accompanying" query was expected to come in, but did not.
+ # Assume the client never sends "accompanying" queries to
+ # prevent pointlessly waiting for them ever again.
+ $no_more_waiting = 1;
+ # Send the delayed reply to the query which caused us to wait.
+ send_delayed_response();
+ }
+}
diff --git a/bin/tests/system/reclimit/ans7/ans.pl b/bin/tests/system/reclimit/ans7/ans.pl
new file mode 100644
index 0000000..5050427
--- /dev/null
+++ b/bin/tests/system/reclimit/ans7/ans.pl
@@ -0,0 +1,74 @@
+#!/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 $count = 0;
+
+my $localaddr = "10.53.0.7";
+my $localport = int($ENV{'PORT'});
+if (!$localport) { $localport = 5300; }
+my $verbose = 0;
+
+sub reply_handler {
+ my ($qname, $qclass, $qtype, $peerhost, $query, $conn) = @_;
+ my ($rcode, @ans, @auth, @add);
+
+ print ("request: $qname/$qtype\n");
+ STDOUT->flush();
+
+ $count += 1;
+
+ if ($qname eq "count" ) {
+ if ($qtype eq "TXT") {
+ my ($ttl, $rdata) = (0, "$count");
+ my $rr = new Net::DNS::RR("$qname $ttl $qclass $qtype $rdata");
+ push @ans, $rr;
+ print ("\tcount: $count\n");
+ }
+ $rcode = "NOERROR";
+ } elsif ($qname eq "reset") {
+ $count = 0;
+ $rcode = "NOERROR";
+ } else {
+ $rcode = "REFUSED";
+ }
+
+ # mark the answer as authoritive (by setting the 'aa' flag
+ 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/reclimit/clean.sh b/bin/tests/system/reclimit/clean.sh
new file mode 100644
index 0000000..f856c86
--- /dev/null
+++ b/bin/tests/system/reclimit/clean.sh
@@ -0,0 +1,18 @@
+#!/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*
+rm -f ans?/ans.run
+rm -f ans2/ans.limit
+rm -f ns?/named.memstats
+rm -f ns?/named.run
+rm -f ns*/named.conf
+rm -f ns*/named.lock
diff --git a/bin/tests/system/reclimit/ns1/named.conf.in b/bin/tests/system/reclimit/ns1/named.conf.in
new file mode 100644
index 0000000..f8868f5
--- /dev/null
+++ b/bin/tests/system/reclimit/ns1/named.conf.in
@@ -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.
+ */
+
+options {
+ directory ".";
+ 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;
+};
+
+zone "." { type master; file "root.db"; };
diff --git a/bin/tests/system/reclimit/ns1/root.db b/bin/tests/system/reclimit/ns1/root.db
new file mode 100644
index 0000000..6a04ed2
--- /dev/null
+++ b/bin/tests/system/reclimit/ns1/root.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.
+
+. 60 IN SOA ns.nil. hostmaster.ns.nil. 1 0 0 0 0
+. 60 IN NS ns.nil.
+ns.nil. 60 IN A 10.53.0.1
+ns.tld1. 60 IN A 10.53.0.1
+example.org. 60 IN NS direct.example.org.
+direct.example.org. 60 IN A 10.53.0.2
+example.net. 60 IN NS direct.example.net.
+direct.example.net. 60 IN A 10.53.0.2
diff --git a/bin/tests/system/reclimit/ns3/hints.db b/bin/tests/system/reclimit/ns3/hints.db
new file mode 100644
index 0000000..f763729
--- /dev/null
+++ b/bin/tests/system/reclimit/ns3/hints.db
@@ -0,0 +1,11 @@
+; 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.
+
+. 60 IN NS ns.nil.
+ns.nil. 60 IN A 10.53.0.1
diff --git a/bin/tests/system/reclimit/ns3/named1.conf.in b/bin/tests/system/reclimit/ns3/named1.conf.in
new file mode 100644
index 0000000..f7f6c6a
--- /dev/null
+++ b/bin/tests/system/reclimit/ns3/named1.conf.in
@@ -0,0 +1,34 @@
+/*
+ * 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.3;
+ notify-source 10.53.0.3;
+ transfer-source 10.53.0.3;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ servfail-ttl 0;
+ max-recursion-depth 12;
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm hmac-sha256;
+};
+
+controls {
+ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." { type hint; file "hints.db"; };
diff --git a/bin/tests/system/reclimit/ns3/named2.conf.in b/bin/tests/system/reclimit/ns3/named2.conf.in
new file mode 100644
index 0000000..f051cf5
--- /dev/null
+++ b/bin/tests/system/reclimit/ns3/named2.conf.in
@@ -0,0 +1,34 @@
+/*
+ * 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.3;
+ notify-source 10.53.0.3;
+ transfer-source 10.53.0.3;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ servfail-ttl 0;
+ max-recursion-depth 5;
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm hmac-sha256;
+};
+
+controls {
+ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." { type hint; file "hints.db"; };
diff --git a/bin/tests/system/reclimit/ns3/named3.conf.in b/bin/tests/system/reclimit/ns3/named3.conf.in
new file mode 100644
index 0000000..6c235af
--- /dev/null
+++ b/bin/tests/system/reclimit/ns3/named3.conf.in
@@ -0,0 +1,35 @@
+/*
+ * 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.3;
+ notify-source 10.53.0.3;
+ transfer-source 10.53.0.3;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ servfail-ttl 0;
+ max-recursion-depth 100;
+ max-recursion-queries 50;
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm hmac-sha256;
+};
+
+controls {
+ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." { type hint; file "hints.db"; };
diff --git a/bin/tests/system/reclimit/ns3/named4.conf.in b/bin/tests/system/reclimit/ns3/named4.conf.in
new file mode 100644
index 0000000..2d7d5c1
--- /dev/null
+++ b/bin/tests/system/reclimit/ns3/named4.conf.in
@@ -0,0 +1,35 @@
+/*
+ * 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.3;
+ notify-source 10.53.0.3;
+ transfer-source 10.53.0.3;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ servfail-ttl 0;
+ max-recursion-depth 100;
+ max-recursion-queries 40;
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm hmac-sha256;
+};
+
+controls {
+ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." { type hint; file "hints.db"; };
diff --git a/bin/tests/system/reclimit/prereq.sh b/bin/tests/system/reclimit/prereq.sh
new file mode 100644
index 0000000..3c854d2
--- /dev/null
+++ b/bin/tests/system/reclimit/prereq.sh
@@ -0,0 +1,35 @@
+#!/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
+
+if $PERL -e 'use Net::DNS;' 2>/dev/null
+then
+ if $PERL -e 'use Net::DNS; die if ($Net::DNS::VERSION <= 0.78);' 2>/dev/null
+ then
+ :
+ else
+ echo_i "Net::DNS versions up to 0.78 have a bug that causes this test to fail: please update." >&2
+ exit 1
+ fi
+else
+ echo_i "This test requires the 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/reclimit/setup.sh b/bin/tests/system/reclimit/setup.sh
new file mode 100644
index 0000000..a191bf8
--- /dev/null
+++ b/bin/tests/system/reclimit/setup.sh
@@ -0,0 +1,17 @@
+#!/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
+copy_setports ns1/named.conf.in ns1/named.conf
+copy_setports ns3/named1.conf.in ns3/named.conf
diff --git a/bin/tests/system/reclimit/tests.sh b/bin/tests/system/reclimit/tests.sh
new file mode 100644
index 0000000..bafdc82
--- /dev/null
+++ b/bin/tests/system/reclimit/tests.sh
@@ -0,0 +1,191 @@
+#!/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
+
+DIGOPTS="-p ${PORT}"
+
+status=0
+n=0
+
+ns3_reset() {
+ copy_setports $1 ns3/named.conf
+ $RNDC -c ../common/rndc.conf -s 10.53.0.3 -p ${CONTROLPORT} reconfig 2>&1 | sed 's/^/I:ns3 /'
+ $RNDC -c ../common/rndc.conf -s 10.53.0.3 -p ${CONTROLPORT} flush | sed 's/^/I:ns3 /'
+}
+
+ns3_sends_aaaa_queries() {
+ if grep "started AAAA fetch" ns3/named.run >/dev/null; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+# Check whether the number of queries ans2 received from ns3 (this value is
+# read from dig output stored in file $1) is as expected. The expected query
+# count is variable:
+# - if ns3 sends AAAA queries, the query count should equal $2,
+# - if ns3 does not send AAAA queries, the query count should equal $3.
+check_query_count() {
+ count=`sed 's/[^0-9]//g;' $1`
+ expected_count_with_aaaa=$2
+ expected_count_without_aaaa=$3
+
+ if ns3_sends_aaaa_queries; then
+ expected_count=$expected_count_with_aaaa
+ else
+ expected_count=$expected_count_without_aaaa
+ fi
+
+ if [ $count -ne $expected_count ]; then
+ echo_i "count ($count) != $expected_count"
+ ret=1
+ fi
+}
+
+echo_i "set max-recursion-depth=12"
+
+n=`expr $n + 1`
+echo_i "attempt excessive-depth lookup ($n)"
+ret=0
+echo "1000" > ans2/ans.limit
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect1.example.org > dig.out.1.test$n || ret=1
+grep "status: SERVFAIL" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+check_query_count dig.out.2.test$n 26 14
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "attempt permissible lookup ($n)"
+ret=0
+echo "12" > ans2/ans.limit
+ns3_reset ns3/named1.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect2.example.org > dig.out.1.test$n || ret=1
+grep "status: NOERROR" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+check_query_count dig.out.2.test$n 49 26
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+echo_i "set max-recursion-depth=5"
+
+n=`expr $n + 1`
+echo_i "attempt excessive-depth lookup ($n)"
+ret=0
+echo "12" > ans2/ans.limit
+ns3_reset ns3/named2.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect3.example.org > dig.out.1.test$n || ret=1
+grep "status: SERVFAIL" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+check_query_count dig.out.2.test$n 12 7
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "attempt permissible lookup ($n)"
+ret=0
+echo "5" > ans2/ans.limit
+ns3_reset ns3/named2.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect4.example.org > dig.out.1.test$n || ret=1
+grep "status: NOERROR" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+check_query_count dig.out.2.test$n 21 12
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+echo_i "set max-recursion-depth=100, max-recursion-queries=50"
+
+n=`expr $n + 1`
+echo_i "attempt excessive-queries lookup ($n)"
+ret=0
+echo "13" > ans2/ans.limit
+ns3_reset ns3/named3.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect5.example.org > dig.out.1.test$n || ret=1
+if ns3_sends_aaaa_queries; then
+ grep "status: SERVFAIL" dig.out.1.test$n > /dev/null || ret=1
+fi
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+eval count=`cat dig.out.2.test$n`
+[ $count -le 50 ] || { ret=1; echo_i "count ($count) !<= 50"; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "attempt permissible lookup ($n)"
+ret=0
+echo "12" > ans2/ans.limit
+ns3_reset ns3/named3.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect6.example.org > dig.out.1.test$n || ret=1
+grep "status: NOERROR" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+eval count=`cat dig.out.2.test$n`
+[ $count -le 50 ] || { ret=1; echo_i "count ($count) !<= 50"; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+echo_i "set max-recursion-depth=100, max-recursion-queries=40"
+
+n=`expr $n + 1`
+echo_i "attempt excessive-queries lookup ($n)"
+ret=0
+echo "10" > ans2/ans.limit
+ns3_reset ns3/named4.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect7.example.org > dig.out.1.test$n || ret=1
+if ns3_sends_aaaa_queries; then
+ grep "status: SERVFAIL" dig.out.1.test$n > /dev/null || ret=1
+fi
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+eval count=`cat dig.out.2.test$n`
+[ $count -le 40 ] || { ret=1; echo_i "count ($count) !<= 40"; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "attempt permissible lookup ($n)"
+ret=0
+echo "9" > ans2/ans.limit
+ns3_reset ns3/named4.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS @10.53.0.3 indirect8.example.org > dig.out.1.test$n || ret=1
+grep "status: NOERROR" dig.out.1.test$n > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+eval count=`cat dig.out.2.test$n`
+[ $count -le 40 ] || { ret=1; echo_i "count ($count) !<= 40"; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+n=`expr $n + 1`
+echo_i "attempting NS explosion ($n)"
+ret=0
+ns3_reset ns3/named4.conf.in
+$DIG $DIGOPTS @10.53.0.2 reset > /dev/null || ret=1
+$DIG $DIGOPTS +short @10.53.0.3 ns1.1.example.net > dig.out.1.test$n || ret=1
+$DIG $DIGOPTS +short @10.53.0.2 count txt > dig.out.2.test$n || ret=1
+eval count=`cat dig.out.2.test$n`
+[ $count -lt 50 ] || ret=1
+$DIG $DIGOPTS +short @10.53.0.7 count txt > dig.out.3.test$n || ret=1
+eval count=`cat dig.out.3.test$n`
+[ $count -lt 50 ] || { ret=1; echo_i "count ($count) !<= 50"; }
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=`expr $status + $ret`
+
+echo_i "exit status: $status"
+[ $status -eq 0 ] || exit 1