diff --git a/CHANGES b/CHANGES index 8d4af0c7e7..5e9f168f9d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,9 @@ +4116. [bug] Fix a bug in RPZ that could cause some policy + zones that did not specifically require + recursion to be treated as if they did; + consequently, setting qname-wait-recurse no; was + sometimes ineffective. [RT #39229] + 4115. [func] "rndc -r" now prints the result code (e.g., ISC_R_SUCCESS, ISC_R_TIMEOUT, etc) after running the requested command. [RT #38913] diff --git a/bin/tests/system/README b/bin/tests/system/README index 69a3937ac8..0ed340e632 100644 --- a/bin/tests/system/README +++ b/bin/tests/system/README @@ -19,6 +19,7 @@ involving a different DNS setup. They are: (not a complete resolver test suite) rrl/ query rate limiting rpz/ Tests of response policy zone (RPZ) rewriting + rpzrecurse/ Another set of RPZ tests to check recursion behavior stub/ Tests of stub zone functionality unknown/ Unknown type and class tests upforwd/ Update forwarding tests diff --git a/bin/tests/system/conf.sh.in b/bin/tests/system/conf.sh.in index 1b206caecf..d0f6bbcf17 100644 --- a/bin/tests/system/conf.sh.in +++ b/bin/tests/system/conf.sh.in @@ -72,8 +72,9 @@ SUBDIRS="acl additional allow_query addzone autosign builtin legacy limits logfileconfig lwresd masterfile masterformat metadata mkeys notify nslookup nsupdate pending pipelined @PKCS11_TEST@ - reclimit redirect resolver rndc rpz rrl rrchecker rrsetorder - rsabigexponent runtime sit sfcache smartsign sortlist spf + reclimit redirect resolver rndc rpz rpzrecurse + rrl rrchecker rrsetorder rsabigexponent runtime + sit sfcache smartsign sortlist spf staticstub statistics stub tcp tkey tsig tsiggss unknown upforwd verify views wildcard xfer xferquota zero zonechecks" diff --git a/bin/tests/system/rpz/tests.sh b/bin/tests/system/rpz/tests.sh index 52aaf633bb..4baa30a399 100644 --- a/bin/tests/system/rpz/tests.sh +++ b/bin/tests/system/rpz/tests.sh @@ -635,7 +635,7 @@ for i in 1 2 3 4 5; do nsd $ns5 delete '*.example.com.policy1.' example.com.policy1. done -echo "I:checking checking that going from a empty policy zone works" +echo "I:checking that going from a empty policy zone works" nsd $ns5 add '*.x.servfail.policy2.' x.servfail.policy2. sleep 1 $RNDCCMD $ns7 reload policy2 diff --git a/bin/tests/system/rpzrecurse/.gitignore b/bin/tests/system/rpzrecurse/.gitignore new file mode 100644 index 0000000000..6154e06877 --- /dev/null +++ b/bin/tests/system/rpzrecurse/.gitignore @@ -0,0 +1,2 @@ +/rpz +/rpz.o diff --git a/bin/tests/system/rpzrecurse/README b/bin/tests/system/rpzrecurse/README new file mode 100644 index 0000000000..6312006b68 --- /dev/null +++ b/bin/tests/system/rpzrecurse/README @@ -0,0 +1,113 @@ +These tests check RPZ recursion behavior (including skipping +recursion when appropriate). + +The general structure of the tests is: + +* The resolver (ns2) with an unqualified view containing the policy + zones, the response-policy statement, and a root hint zone + +* The auth server that contains two authoritative zones, l1.l0 and + l2.l1.l0, both delegated to itself. l2.l1.l0 specifies a non-existent + zone data file and so will generate SERVFAILs for any queries to it. + +The l2.l1.l0 zone was chosen to generate SERVFAIL responses because RPZ +evaluation will use that error response whenever it encounters it during +processing, thus making it a binary indicator for whether or not +recursion was attempted. This also allows us to not worry about having +to craft 'ip', 'nsdname', and 'nsip' rules that matched the queries. + +Each test is intended to be fed a number of queries constructed as +qXX.l2.l1.l0, where XX is the 1-based query sequence number (e.g. the +first query of each test is q01.l2.l1.l0). + +For all the tests the triggers are constructed as follows: +client-ip - match 127.0.0.1/32 +ip - match 255.255.255.255/32 (does not matter due to SERVFAIL) +nsdname - match does.not.matter (and it doesn't) +nsip - match 255.255.255.255/32 (also does not matter) +qname - match qXX.l2.l1.l0, where XX is the query sequence number that +is intended to be matched by this qname rule. + +Here's the detail on the test cases: + +Group 1 - testing skipping recursion for a single policy zone with only +records that allow recursion to be skipped + +Test 1a: + 1 policy zone containing 1 'client-ip' trigger + 1 query, expected to skip recursion + +Test 1b: + 1 policy zone containing 1 'qname' trigger (q01) + 2 queries, q01 is expected to skip recursion, q02 is expected to + recurse + +Test 1c: + 1 policy zone containing both a 'client-ip' and 'qname' trigger (q02) + 1 query, expected to skip recursion + +Group 2 - testing skipping recursion with multiple policy zones when all +zones have only trigger types eligible to skip recursion with + +Test 2a: + 32 policy zones, each containing 1 'qname' trigger (qNN, where NN is + the zone's sequence 1-based sequence number formatted to 2 digits, + so each of the first 32 queries should match a different zone) + 33 queries, the first 32 of which are expected to skip recursion + while the 33rd is expected to recurse + +Group 3 - Testing interaction of triggers that require recursion when in +a single zone, both alone and with triggers that allow recursion to be +skipped + +Test 3a: + 1 policy zone containing 1 'ip' trigger + 1 query, expected to recurse + +Test 3b: + 1 policy zone containing 1 'nsdname' trigger + 1 query, expected to recurse + +Test 3c: + 1 policy zone containing 1 'nsip' trigger + 1 query, expected to recurse + +Test 3d: + 1 policy zone containing 1 'ip' trigger and 1 'qname' trigger (q02) + 2 queries, the first should not recurse and the second should recurse + +Test 3e: + 1 policy zone containing 1 'nsdname' trigger and 1 'qname' trigger + (q02) + 2 queries, the first should not recurse and the second should recurse + +Test 3f: + 1 policy zone containing 1 'nsip' trigger and 1 'qname' trigger (q02) + 2 queries, the first should not recurse and the second should recurse + +Group 4 - contains 32 subtests designed to verify that recursion is +skippable for only the appropriate zones based on the order specified in +the 'response-policy' statement + +Tests 4aa to 4bf: + 32 policy zones per test, one of which is configured with 1 'ip' + trigger and one 'qname' trigger while the others are configured + only with 1 'qname' trigger. The zone with both triggers starts + listed first and is moved backwards by one position with each + test. The 'qname' triggers in the zones are structured so that + the zones are tested starting with the first zone and the 'ip' + trigger is tested before the 'qname' trigger for that zone. + 33 queries per test, where the number expected to skip recursion + matches the test sequence number: e.g. 1 skip for 4aa, 26 skips + for 4az, and 32 skips for 4bf + +Group 5 - This test verifies that the "pivot" policy zone for whether or +not recursion can be skipped is the first listed zone with applicable +trigger types rather than a later listed zone. + +Test 5a: + 5 policy zones, the 1st, 3rd, and 5th configured with 1 'qname' + trigger each (q01, q04, and q06, respectively), the 2nd and 4th + each configured with an 'ip' and 'qname' trigger (q02 and q05, + respectively for the 'qname' triggers + 6 queries, of which only q01 and q02 are expected to skip recursion diff --git a/bin/tests/system/rpzrecurse/clean.sh b/bin/tests/system/rpzrecurse/clean.sh new file mode 100644 index 0000000000..e8cf5aa414 --- /dev/null +++ b/bin/tests/system/rpzrecurse/clean.sh @@ -0,0 +1,24 @@ +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +# Clean up after rpz tests. + +rm -f dig.out.* +rm -f ns2/named.conf +rm -f ns2/*.local +rm -f ns2/*.queries +rm -f ns2/named.*.conf +rm -f ns*/named.lock +rm -f ns*/named.memstats +rm -f ns*/named.run diff --git a/bin/tests/system/rpzrecurse/ns1/db.l0 b/bin/tests/system/rpzrecurse/ns1/db.l0 new file mode 100644 index 0000000000..3fd996fde5 --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns1/db.l0 @@ -0,0 +1,6 @@ +$TTL 60 +@ IN SOA root.ns ns 1996072700 3600 1800 86400 60 + NS ns +ns A 10.53.0.1 +l1 NS ns.l1 +ns.l1 A 10.53.0.1 diff --git a/bin/tests/system/rpzrecurse/ns1/db.l1.l0 b/bin/tests/system/rpzrecurse/ns1/db.l1.l0 new file mode 100644 index 0000000000..cb3c4323dd --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns1/db.l1.l0 @@ -0,0 +1,6 @@ +$TTL 60 +@ IN SOA root.ns ns 1996072700 3600 1800 86400 60 + NS ns +ns A 10.53.0.1 +l2 NS ns.l2 +ns.l2 A 10.53.0.1 diff --git a/bin/tests/system/rpzrecurse/ns1/named.conf b/bin/tests/system/rpzrecurse/ns1/named.conf new file mode 100644 index 0000000000..4de9d81610 --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns1/named.conf @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +options { + query-source address 10.53.0.1; + notify-source 10.53.0.1; + transfer-source 10.53.0.1; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.1; }; + listen-on-v6 { none; }; + recursion no; +}; + +zone "." { + type master; + file "root.db"; +}; + +zone "l0" { + type master; + file "db.l0"; +}; + +zone "l1.l0" { + type master; + file "db.l1.l0"; +}; + +zone "l2.l1.l0" { + type master; + file "does-not-exist"; +}; diff --git a/bin/tests/system/rpzrecurse/ns1/root.db b/bin/tests/system/rpzrecurse/ns1/root.db new file mode 100644 index 0000000000..79b7f48768 --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns1/root.db @@ -0,0 +1,27 @@ +; Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +$TTL 300 +. IN SOA muks.isc.org. a.root.servers.nil. ( + 2010 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +l0. NS ns.l0. +ns.l0. A 10.53.0.1 diff --git a/bin/tests/system/rpzrecurse/ns2/.gitignore b/bin/tests/system/rpzrecurse/ns2/.gitignore new file mode 100644 index 0000000000..db4f2c3d45 --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns2/.gitignore @@ -0,0 +1,3 @@ +/*.local +/*.queries +/*.conf diff --git a/bin/tests/system/rpzrecurse/ns2/named.conf.default b/bin/tests/system/rpzrecurse/ns2/named.conf.default new file mode 100644 index 0000000000..b92af5b0cd --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns2/named.conf.default @@ -0,0 +1,9 @@ +# common configuration +include "named.conf.header"; + +view "recursive" { + zone "." { + type hint; + file "root.hint"; + }; +}; diff --git a/bin/tests/system/rpzrecurse/ns2/named.conf.header b/bin/tests/system/rpzrecurse/ns2/named.conf.header new file mode 100644 index 0000000000..7a78a1a617 --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns2/named.conf.header @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { /* empty */ }; + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port 5300; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion yes; + querylog yes; +}; diff --git a/bin/tests/system/rpzrecurse/ns2/root.hint b/bin/tests/system/rpzrecurse/ns2/root.hint new file mode 100644 index 0000000000..5f9268ef6f --- /dev/null +++ b/bin/tests/system/rpzrecurse/ns2/root.hint @@ -0,0 +1,17 @@ +; Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +; +; Permission to use, copy, modify, and/or distribute this software for any +; purpose with or without fee is hereby granted, provided that the above +; copyright notice and this permission notice appear in all copies. +; +; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +; AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +; PERFORMANCE OF THIS SOFTWARE. + +$TTL 999999 +. IN NS a.root-servers.nil. +a.root-servers.nil. IN A 10.53.0.1 diff --git a/bin/tests/system/rpzrecurse/prereq.sh b/bin/tests/system/rpzrecurse/prereq.sh new file mode 100644 index 0000000000..1ce466e097 --- /dev/null +++ b/bin/tests/system/rpzrecurse/prereq.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +ret=0 +../rpz/rpz nsdname || ret=1 +../rpz/rpz nsip || ret=1 + +if [ $ret != 0 ]; then + echo "I:This test requires NSIP AND NSDNAME support in RPZ." >&2 + exit 1 +fi + +exec $SHELL ../testcrypto.sh diff --git a/bin/tests/system/rpzrecurse/setup.sh b/bin/tests/system/rpzrecurse/setup.sh new file mode 100644 index 0000000000..100464bf1e --- /dev/null +++ b/bin/tests/system/rpzrecurse/setup.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +perl testgen.pl +cp -f ns2/named.conf.default ns2/named.conf diff --git a/bin/tests/system/rpzrecurse/testgen.pl b/bin/tests/system/rpzrecurse/testgen.pl new file mode 100755 index 0000000000..6062a24f42 --- /dev/null +++ b/bin/tests/system/rpzrecurse/testgen.pl @@ -0,0 +1,299 @@ +#!/usr/bin/env perl + +use strict; +use warnings; + +my $boilerplate_header = <<'EOB'; +# common configuration +include "named.conf.header"; + +view "recursive" { + zone "." { + type hint; + file "root.hint"; + }; + + # policy configuration to be tested + response-policy { +EOB + +my $boilerplate_middle = <<'EOB'; + } qname-wait-recurse no; + + # policy zones to be tested +EOB + +my $boilerplate_end = <<'EOB'; +}; +EOB + +my $policy_zone_header = <<'EOH'; +$TTL 60 +@ IN SOA root.ns ns 1996072700 3600 1800 86400 60 + NS ns +ns A 127.0.0.1 +EOH + +sub policy_client_ip { + return "32.1.0.0.127.rpz-client-ip CNAME .\n"; +} + +sub policy_qname { + my $query_nbr = shift; + return sprintf "q%02d.l2.l1.l0 CNAME .\n", $query_nbr; +} + +sub policy_ip { + return "32.255.255.255.255.rpz-ip CNAME .\n"; +} + +sub policy_nsdname { + return "does.not.matter.rpz-nsdname CNAME .\n"; +} + +sub policy_nsip { + return "32.255.255.255.255.rpz-ip CNAME .\n"; +} + +my %static_triggers = ( + 'client-ip' => \&policy_client_ip, + 'ip' => \&policy_ip, + 'nsdname' => \&policy_nsdname, + 'nsip' => \&policy_nsip, +); + +sub mkconf { + my $case_id = shift; + my $n_queries = shift; + + { # generate the query list + my $query_list_filename = "ns2/$case_id.queries"; + my $query_list_fh; + + open $query_list_fh, ">$query_list_filename" or die; + + for( my $i = 1; $i <= $n_queries; $i++ ) { + print $query_list_fh sprintf "q%02d.l2.l1.l0\n", $i; + } + } + + my @zones; + + { # generate the conf file + my $conf_filename = "ns2/named.$case_id.conf"; + + my $conf_fh; + + open $conf_fh, ">$conf_filename" or die; + + print $conf_fh $boilerplate_header; + + my $zone_seq = 0; + + @zones = map { + [ + sprintf( "$case_id.%02d.policy.local", $zone_seq++ ), + $_, + ]; + } @_; + + print $conf_fh map { qq{ zone "$_->[0]";\n} } @zones; + + print $conf_fh $boilerplate_middle; + + print $conf_fh map { qq{ zone "$_->[0]" { type master; file "db.$_->[0]"; };\n} } @zones; + + print $conf_fh $boilerplate_end; + } + + # generate the policy zone contents + foreach my $policy_zone_info( @zones ) { + my $policy_zone_name = $policy_zone_info->[0]; + my $policy_zone_contents = $policy_zone_info->[1]; + + my $policy_zone_filename = "ns2/db.$policy_zone_name"; + my $policy_zone_fh; + + open $policy_zone_fh, ">$policy_zone_filename" or die; + + print $policy_zone_fh $policy_zone_header; + + foreach my $trigger( @$policy_zone_contents ) { + if( exists $static_triggers{$trigger} ) { + # matches a trigger type with a static value + print $policy_zone_fh $static_triggers{$trigger}->(); + } + else { + # a qname trigger, where what was specified is the query number it should match + print $policy_zone_fh policy_qname( $trigger ); + } + } + } +} + +mkconf( + '1a', + 1, + [ 'client-ip' ], +); + +mkconf( + '1b', + 2, + [ 1 ], +); + +mkconf( + '1c', + 1, + [ 'client-ip', 2 ], +); + +mkconf( + '2a', + 33, + map { [ $_ ]; } 1 .. 32 +); + +mkconf( + '3a', + 1, + [ 'ip' ], +); + +mkconf( + '3b', + 1, + [ 'nsdname' ], +); + +mkconf( + '3c', + 1, + [ 'nsip' ], +); + +mkconf( + '3d', + 2, + [ 'ip', 1 ] +); + +mkconf( + '3e', + 2, + [ 'nsdname', 1 ] +); + +mkconf( + '3f', + 2, + [ 'nsip', 1 ] +); + +{ + my $seq_code = 'aa'; + my $seq_nbr = 0; + + while( $seq_nbr < 32 ) { + + mkconf( + "4$seq_code", + 33, + ( map { [ $_ ]; } 1 .. $seq_nbr ), + [ 'ip', $seq_nbr + 2 ], + ( map { [ $_ + 2 ]; } ($seq_nbr + 1) .. 31 ), + ); + + $seq_code++; + $seq_nbr++; + } +} + +mkconf( + '5a', + 6, + [ 1 ], + [ 2, 'ip' ], + [ 4 ], + [ 5, 'ip' ], + [ 6 ], +); + +__END__ + +0x01 - has client-ip + 32.1.0.0.127.rpz-client-ip CNAME . +0x02 - has qname + qX.l2.l1.l0 CNAME . +0x10 - has ip + 32.255.255.255.255.rpz-ip CNAME . +0x20 - has nsdname + does.not.matter.rpz-nsdname CNAME . +0x40 - has nsip + 32.255.255.255.255.rpz-nsip CNAME . + +$case.$seq.policy.local + +case 1a = 0x01 + .q01 = (00,0x01)=-r +case 1b = 0x02 + .q01 = (00,0x02)=-r + .q02 = (--,----)=+r +case 1c = 0x03 + .q01 = (00,0x01)=-r + +case 2a = 0x03{32} + .q01 = (00,0x02)=-r + .q02 = (01,0x02)=-r + ... + .q31 = (30,0x02)=-r + .q32 = (31,0x02)=-r + .q33 = (--,----)=+r + +case 3a = 0x10 + .q01 = (00,0x10)=+r +case 3b = 0x20 + .q01 = (00,0x20)=+r +case 3c = 0x40 + .q01 = (00,0x40)=+r +case 3d = 0x12 + .q01 = (00,0x10)=+r + .q02 = (00,0x02)=-r +case 3e = 0x22 + .q01 = (00,0x20)=+r + .q02 = (00,0x02)=-r +case 3f = 0x42 + .q01 = (00,0x40)=+r + .q02 = (00,0x02)=-r + +case 4aa = 0x12,0x02{31} + .q01 = (00,0x10)=+r + .q02 = (00,0x02)=-r + .q03 = (01,0x02)=+r + ... + .q32 = (30,0x02)=+r + .q33 = (31,0x02)=+r +case 4__ = 0x02{n(1->30)},0x12,0x02{31-n} + .q01 = (00,0x02)=-r + ... + .q(n+1) = (n,0x10)=+r + .q(n+2) = (n,0x02)=-r + ... + .q33 = (31,0x02)=+r +case 4bf = 0x02{31},0x12 + .q01 = (00,0x02)=-r + .q02 = (01,0x02)=-r + ... + .q31 = (30,0x02)=-r + .q32 = (31,0x10)=+r + .q33 = (31,0x02)=-r + +case 5a = 0x02,0x12,0x02,0x12,0x02 + .q01 = (00,0x02)=-r + .q02 = (01,0x02)=-r + .q03 = (01,0x10)=+r + .q04 = (02,0x02)=+r + .q05 = (03,0x02)=+r + .q06 = (04,0x02)=+r + diff --git a/bin/tests/system/rpzrecurse/tests.sh b/bin/tests/system/rpzrecurse/tests.sh new file mode 100644 index 0000000000..0e6cb384ab --- /dev/null +++ b/bin/tests/system/rpzrecurse/tests.sh @@ -0,0 +1,168 @@ +#!/bin/sh +# +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +status=0 +t=0 + +# $1 = test name (such as 1a, 1b, etc. for which named.$1.conf exists) +run_server() { + TESTNAME=$1 + + echo "I:stopping resolver" + $PERL $SYSTEMTESTTOP/stop.pl . ns2 + + sleep 1 + + echo "I:starting resolver using named.$TESTNAME.conf" + cp -f ns2/named.$TESTNAME.conf ns2/named.conf + $PERL $SYSTEMTESTTOP/start.pl --noclean --restart . ns2 +} + +run_query() { + TESTNAME=$1 + LINE=$2 + + NAME=`tail -n +"$LINE" ns2/$TESTNAME.queries | head -n 1` + $DIG $DIGOPTS $NAME a @10.53.0.2 -p 5300 -b 127.0.0.1 > dig.out.${t} + grep "status: SERVFAIL" dig.out.${t} > /dev/null 2>&1 && return 1 + return 0 +} + +# $1 = test name (such as 1a, 1b, etc. for which $1.queries exists) +# $2 = line number in query file to test (the name to query is taken from this line) +expect_norecurse() { + TESTNAME=$1 + LINE=$2 + + NAME=`tail -n +"$LINE" ns2/$TESTNAME.queries | head -n 1` + t=`expr $t + 1` + echo "I:testing $NAME doesn't recurse (${t})" + run_query $TESTNAME $LINE || { + echo "I:test ${t} failed" + status=1 + } +} + +# $1 = test name (such as 1a, 1b, etc. for which $1.queries exists) +# $2 = line number in query file to test (the name to query is taken from this line) +expect_recurse() { + TESTNAME=$1 + LINE=$2 + + NAME=`tail -n +"$LINE" ns2/$TESTNAME.queries | head -n 1` + t=`expr $t + 1` + echo "I:testing $NAME recurses (${t})" + run_query $TESTNAME $LINE && { + echo "I:test $t failed" + status=1 + } +} + +t=`expr $t + 1` +echo "I:testing that l1.l0 exists without RPZ (${t})" +$DIG $DIGOPTS l1.l0 ns @10.53.0.2 -p 5300 > dig.out.${t} +grep "status: NOERROR" dig.out.${t} > /dev/null 2>&1 || { + echo "I:test $t failed" + status=1 +} + +t=`expr $t + 1` +echo "I:testing that l2.l1.l0 returns SERVFAIL without RPZ (${t})" +$DIG $DIGOPTS l2.l1.l0 ns @10.53.0.2 -p 5300 > dig.out.${t} +grep "status: SERVFAIL" dig.out.${t} > /dev/null 2>&1 || { + echo "I:test $t failed" + status=1 +} + +# Group 1 +run_server 1a +expect_norecurse 1a 1 +run_server 1b +expect_norecurse 1b 1 +expect_recurse 1b 2 +run_server 1c +expect_norecurse 1c 1 + +# Group 2 +run_server 2a +for n in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 +do + expect_norecurse 2a $n +done +expect_recurse 2a 33 + +# Group 3 +run_server 3a +expect_recurse 3a 1 +run_server 3b +expect_recurse 3b 1 +run_server 3c +expect_recurse 3c 1 +run_server 3d +expect_norecurse 3d 1 +expect_recurse 3d 2 +run_server 3e +expect_norecurse 3e 1 +expect_recurse 3e 2 +run_server 3f +expect_norecurse 3f 1 +expect_recurse 3f 2 + +# Group 4 +testlist="aa ap bf" +values="1 16 32" +# Uncomment the following to test every skip value instead of +# only a sample of values +# +#testlist="aa ab ac ad ae af ag ah ai aj ak al am an ao ap \ +# aq ar as at au av aw ax ay az ba bb bc bd be bf" +#values="1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ +# 21 22 23 24 25 26 27 28 29 30 31 32" +set -- $values +for n in $testlist; do + run_server 4$n + ni=$1 + t=`expr $t + 1` + echo "I:testing that ${ni} of 33 queries skip recursion (${t})" + c=0 + for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \ + 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 + do + run_query 4$n $i + c=`expr $c + $?` + done + skipped=`expr 33 - $c` + if [ $skipped != $ni ]; then + echo "I:test $t failed (actual=$skipped, expected=$ni)" + status=1 + fi + shift +done + +# Group 5 +run_server 5a +expect_norecurse 5a 1 +expect_norecurse 5a 2 +expect_recurse 5a 3 +expect_recurse 5a 4 +expect_recurse 5a 5 +expect_recurse 5a 6 + +echo "I:exit status: $status" +exit $status diff --git a/doc/arm/Bv9ARM-book.xml b/doc/arm/Bv9ARM-book.xml index 528e2df95d..4490125c95 100644 --- a/doc/arm/Bv9ARM-book.xml +++ b/doc/arm/Bv9ARM-book.xml @@ -9950,7 +9950,7 @@ deny-answer-aliases { "example.net"; }; DISABLED actions) must be chosen. Triggers or the records that encode them are chosen for the rewriting in the following order: - + Choose the triggered record in the zone that appears first in the response-policy option. @@ -9967,7 +9967,7 @@ deny-answer-aliases { "example.net"; }; prefer the IP or NSIP trigger that matches the smallest IP address. - + diff --git a/doc/arm/notes.xml b/doc/arm/notes.xml index 2dea1edc92..57e46ff811 100644 --- a/doc/arm/notes.xml +++ b/doc/arm/notes.xml @@ -620,6 +620,17 @@ dig +short. [RT #39291] + + + A bug in the RPZ implementation could cause some policy + zones that did not specifically require recursion to be + treated as if they did; consequently, setting + qname-wait-recurse no; was + sometimes ineffective. This has been corrected. + In most configurations, behavioral changes due to this + fix will not be noticeable. [RT #39229] + + diff --git a/lib/dns/rpz.c b/lib/dns/rpz.c index bec8330d51..7f9bdd5887 100644 --- a/lib/dns/rpz.c +++ b/lib/dns/rpz.c @@ -55,7 +55,7 @@ * * Each leaf indicates that an IP address is listed in the IP address or the * name server IP address policy sub-zone (or both) of the corresponding - * response response zone. The policy data such as a CNAME or an A record + * response policy zone. The policy data such as a CNAME or an A record * is kept in the policy zone. After an IP address has been found in a radix * tree, the node in the policy zone's database is found by converting * the IP address to a domain name in a canonical form. @@ -133,11 +133,6 @@ struct dns_rpz_cidr_node { dns_rpz_addr_zbits_t sum; }; -/* - * The data in a RBT node has two pairs of bits for policy zones. - * One pair is for the corresponding name of the node such as example.com - * and the other pair is for a wildcard child such as *.example.com. - */ /* * A pair of arrays of bits flagging the existence of * QNAME and NSDNAME policy triggers. @@ -148,6 +143,11 @@ struct dns_rpz_nm_zbits { dns_rpz_zbits_t ns; }; +/* + * The data in a RBT node has two pairs of bits for policy zones. + * One pair is for the corresponding name of the node such as example.com + * and the other pair is for a wildcard child such as *.example.com. + */ typedef struct dns_rpz_nm_data dns_rpz_nm_data_t; struct dns_rpz_nm_data { dns_rpz_nm_zbits_t set; @@ -259,11 +259,15 @@ dns_rpz_policy2str(dns_rpz_policy_t policy) { return (str); } +/* + * Return the bit number of the highest set bit in 'zbit'. + * (for example, 0x01 returns 0, 0xFF returns 7, etc.) + */ static int zbit_to_num(dns_rpz_zbits_t zbit) { dns_rpz_num_t rpz_num; - INSIST(zbit != 0); + REQUIRE(zbit != 0); rpz_num = 0; #if DNS_RPZ_MAX_ZONES > 32 if ((zbit & 0xffffffff00000000L) != 0) { @@ -376,30 +380,172 @@ set_sum_pair(dns_rpz_cidr_node_t *cnode) { static void fix_qname_skip_recurse(dns_rpz_zones_t *rpzs) { - dns_rpz_zbits_t zbits; + dns_rpz_zbits_t mask; + + /* qname_wait_recurse and qname_skip_recurse are used to + * implement the "qname-wait-recurse" config option. + * + * By default, "qname-wait-recurse" is yes, so no + * processing happens without recursion. In this case, + * qname_wait_recurse is true, and qname_skip_recurse + * (a bit field indicating which policy zones can be + * processed without recursion) is set to all 0's by + * fix_qname_skip_recurse(). + * + * When "qname-wait-recurse" is no, qname_skip_recurse may be + * set to a non-zero value by fix_qname_skip_recurse(). The mask + * has to have bits set for for the policy zones for which + * processing may continue without recursion, and bits cleared + * for the rest. + * + * (1) The ARM says: + * + * The "qname-wait-recurse no" option overrides that default + * behavior when recursion cannot change a non-error + * response. The option does not affect QNAME or client-IP + * triggers in policy zones listed after other zones + * containing IP, NSIP and NSDNAME triggers, because those may + * depend on the A, AAAA, and NS records that would be found + * during recursive resolution. + * + * Let's consider the following: + * + * zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 | + * rpzs->have.nsdname | + * rpzs->have.nsipv4 | rpzs->have.nsipv6); + * + * zbits_req now contains bits set for zones which require + * recursion. + * + * But going by the description in the ARM, if the first policy + * zone requires recursion, then all zones after that (higher + * order bits) have to wait as well. If the Nth zone requires + * recursion, then (N+1)th zone onwards all need to wait. + * + * So mapping this, examples: + * + * zbits_req = 0b000 mask = 0xffffffff (no zones have to wait for + * recursion) + * zbits_req = 0b001 mask = 0x00000000 (all zones have to wait) + * zbits_req = 0b010 mask = 0x00000001 (the first zone doesn't have to + * wait, second zone onwards need + * to wait) + * zbits_req = 0b011 mask = 0x00000000 (all zones have to wait) + * zbits_req = 0b100 mask = 0x00000011 (the 1st and 2nd zones don't + * have to wait, third zone + * onwards need to wait) + * + * More generally, we have to count the number of trailing 0 + * bits in zbits_req and only these can be processed without + * recursion. All the rest need to wait. + * + * (2) The ARM says that "qname-wait-recurse no" option + * overrides the default behavior when recursion cannot change a + * non-error response. So, in the order of listing of policy + * zones, within the first policy zone where recursion may be + * required, we should first allow CLIENT-IP and QNAME policy + * records to be attempted without recursion. + */ /* * Get a mask covering all policy zones that are not subordinate to * other policy zones containing triggers that require that the * qname be resolved before they can be checked. */ - if (rpzs->p.qname_wait_recurse) { - zbits = 0; - } else { - zbits = (rpzs->have.ipv4 || rpzs->have.ipv6 || - rpzs->have.nsdname || - rpzs->have.nsipv4 || rpzs->have.nsipv6); - if (zbits == 0) { - zbits = DNS_RPZ_ALL_ZBITS; - } else { - zbits = DNS_RPZ_ZMASK(zbit_to_num(zbits)); - } - } - rpzs->have.qname_skip_recurse = zbits; - rpzs->have.client_ip = rpzs->have.client_ipv4 | rpzs->have.client_ipv6; rpzs->have.ip = rpzs->have.ipv4 | rpzs->have.ipv6; rpzs->have.nsip = rpzs->have.nsipv4 | rpzs->have.nsipv6; + + if (rpzs->p.qname_wait_recurse) { + mask = 0; + } else { + dns_rpz_zbits_t zbits_req; + dns_rpz_zbits_t zbits_notreq; + dns_rpz_zbits_t mask2; + dns_rpz_zbits_t req_mask; + + /* + * Get the masks of zones with policies that + * do/don't require recursion + */ + + zbits_req = (rpzs->have.ipv4 | rpzs->have.ipv6 | + rpzs->have.nsdname | + rpzs->have.nsipv4 | rpzs->have.nsipv6); + zbits_notreq = (rpzs->have.client_ip | rpzs->have.qname); + + if (zbits_req == 0) { + mask = DNS_RPZ_ALL_ZBITS; + goto set; + } + + /* + * req_mask is a mask covering used bits in + * zbits_req. (For instance, 0b1 => 0b1, 0b101 => 0b111, + * 0b11010101 => 0b11111111). + */ + req_mask = zbits_req; + req_mask |= req_mask >> 1; + req_mask |= req_mask >> 2; + req_mask |= req_mask >> 4; + req_mask |= req_mask >> 8; + req_mask |= req_mask >> 16; +#if DNS_RPZ_MAX_ZONES > 32 + req_mask |= req_mask >> 32; +#endif + + /* + * There's no point in skipping recursion for a later + * zone if it is required in a previous zone. + */ + if ((zbits_notreq & req_mask) == 0) { + mask = 0; + goto set; + } + + /* + * This bit arithmetic creates a mask of zones in which + * it is okay to skip recursion. After the first zone + * that has to wait for recursion, all the others have + * to wait as well, so we want to create a mask in which + * all the trailing zeroes in zbits_req are are 1, and + * more significant bits are 0. (For instance, + * 0x0700 => 0x00ff, 0x0007 => 0x0000) + */ + mask = ~(zbits_req | -zbits_req); + + /* + * As mentioned in (2) above, the zone corresponding to + * the least significant zero could have its CLIENT-IP + * and QNAME policies checked before recursion, if it + * has any of those policies. So if it does, we + * can set its 0 to 1. + * + * Locate the least significant 0 bit in the mask (for + * instance, 0xff => 0x100)... + */ + mask2 = (mask << 1) & ~mask; + + /* + * Also set the bit for zone 0, because if it's in + * zbits_notreq then it's definitely okay to attempt to + * skip recursion for zone 0... + */ + mask2 |= 1; + + /* Clear any bits *not* in zbits_notreq... */ + mask2 &= zbits_notreq; + + /* And merge the result into the skip-recursion mask */ + mask |= mask2; + } + + set: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_RPZ, DNS_LOGMODULE_RBTDB, + DNS_RPZ_DEBUG_QUIET, + "computed RPZ qname_skip_recurse mask=0x%llx", + (isc_uint64_t) mask); + rpzs->have.qname_skip_recurse = mask; } static void