diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d334ae1ae5..1c694395b9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -573,7 +573,7 @@ vulture: <<: *precheck_job needs: [] script: - - vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark,reconfigure_policy" bin/tests/system/ + - vulture --exclude "*ans.py,conftest.py,isctest" --ignore-names "pytestmark,reconfigure_policy,setup_filters" bin/tests/system/ ci-variables: <<: *precheck_job diff --git a/bin/tests/system/filters/common.py b/bin/tests/system/filters/common.py new file mode 100644 index 0000000000..a009d31c0a --- /dev/null +++ b/bin/tests/system/filters/common.py @@ -0,0 +1,231 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import dns +from dns import rdataclass, rdatatype + +import isctest + + +ARTIFACTS = [ + "conf/*.conf", + "ns*/trusted.conf", + "ns*/*.signed", + "ns*/K*", + "ns*/dsset-*", + "ns*/signer.err", +] + + +def reconfigure_servers(ftype, family, servers, templates): + for server_id in ["ns1", "ns2", "ns3", "ns4"]: + templates.render( + f"{server_id}/named.conf", {"family": family, "filtertype": ftype} + ) + servers[server_id].reconfigure(log=False) + + +def check_filtertype_only(dest, source, qname, ftype, expected, adflag): + qname = dns.name.from_text(qname) + + msg = isctest.query.create(qname, ftype) + res = isctest.query.tcp(msg, dest, source=source) + isctest.check.noerror(res) + + if adflag: + isctest.check.adflag(res) + else: + isctest.check.noadflag(res) + a_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.A) + aaaa_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.AAAA) + if ftype == "aaaa": + assert not a_record + if expected: + assert ( + aaaa_record and aaaa_record[0].address == expected + ), f"expected AAAA {expected} in ANSWER: {res}" + else: + assert not aaaa_record + if expected: + assert ( + a_record and a_record[0].address == expected + ), f"expected A {expected} in ANSWER: {res}" + + +def check_any(dest, source, qname, expected4, expected6, do): + qname = dns.name.from_text(qname) + msg = isctest.query.create(qname, "any", dnssec=do) + res = isctest.query.tcp(msg, dest, source=source) + isctest.check.noerror(res) + a_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.A) + if expected4: + assert ( + a_record and a_record[0].address == expected4 + ), f"expected A {expected4} in ANSWER: {res}" + else: + assert not a_record + aaaa_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.AAAA) + if expected6: + assert ( + aaaa_record and aaaa_record[0].address == expected6 + ), f"expected AAAA {expected6} in ANSWER: {res}" + else: + assert not aaaa_record + + +def check_nodata(dest, source, qname, qtype, do, adflag): + msg = isctest.query.create(qname, qtype, dnssec=do) + res = isctest.query.tcp(msg, dest, source=source) + isctest.check.noerror(res) + isctest.check.empty_answer(res) + if adflag: + isctest.check.adflag(res) + else: + isctest.check.noadflag(res) + + +def check_additional(dest, source, qname, qtype, ftype, expected, adcount): + msg = isctest.query.create(qname, qtype) + res = isctest.query.tcp(msg, dest, source=source) + isctest.check.noerror(res) + isctest.check.rr_count_eq(res.additional, adcount) + t = rdatatype.A if ftype == "a" else rdatatype.AAAA + if expected: + assert [a for a in res.additional if a.rdtype == t] + else: + assert not [a for a in res.additional if a.rdtype == t] + + +def prime_cache(addr): + isctest.log.debug("prime cache for recursive testing:") + # (when testing recursive, we need to prime the cache first with + # the MX addresses, since additional section data isn't included + # unless it's been validated.) + for name in ["mx", "ns"]: + for zone in ["signed", "unsigned"]: + for qtype in ["a", "aaaa"]: + isctest.log.debug(f"{addr}: {name}.{zone}/{qtype}") + isctest.query.tcp(isctest.query.create(f"{name}.{zone}", qtype), addr) + + +def check_filter(addr, altaddr, ftype, break_dnssec, recursive): + qtype = ftype.upper() + isctest.log.debug( + f"check that {qtype} is returned when only {qtype} record exists, signed" + ) + expected = "1.0.0.2" if ftype == "a" else "2001:db8::2" + check_filtertype_only( + addr, addr, f"{ftype}-only.signed", ftype, expected, recursive + ) + + isctest.log.debug( + f"check that {qtype} is returned when only {qtype} record exists, unsigned" + ) + expected = "1.0.0.5" if ftype == "a" else "2001:db8::5" + check_filtertype_only(addr, addr, f"{ftype}-only.unsigned", ftype, expected, False) + + isctest.log.debug( + "check that NODATA/NOERROR is returned when both AAAA and A exist, signed, DO=0" + ) + check_nodata(addr, addr, "dual.signed", ftype, False, False) + + isctest.log.debug( + "check that NODATA/NOERROR is returned when both AAAA and A exist, unsigned, DO=0" + ) + check_nodata(addr, addr, "dual.unsigned", ftype, False, False) + + isctest.log.debug( + f"check that {qtype} is returned when both AAAA and A exist, signed, DO=1, unless break-dnssec is enabled" + ) + if break_dnssec: + check_nodata(addr, addr, "dual.signed", ftype, False, False) + else: + expected = "1.0.0.3" if ftype == "a" else "2001:db8::3" + check_filtertype_only(addr, addr, "dual.signed", ftype, expected, recursive) + + isctest.log.debug( + "check that NODATA/NOERROR is returned when both AAAA and A exist, unsigned, DO=1" + ) + check_nodata(addr, addr, "dual.unsigned", ftype, recursive, False) + + isctest.log.debug( + f"check that {qtype} is returned if both AAAA and A exist and the query source doesn't match the ACL" + ) + + expected = "1.0.0.6" if ftype == "a" else "2001:db8::6" + check_filtertype_only(addr, altaddr, "dual.unsigned", ftype, expected, False) + + isctest.log.debug( + f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, signed, qtype=ANY, DO=0" + ) + expected4 = "1.0.0.3" if ftype == "aaaa" else None + expected6 = "2001:db8::3" if ftype == "a" else None + check_any(addr, addr, "dual.signed", expected4, expected6, False) + + isctest.log.debug( + "check that both A and AAAA are returned if both AAAA and A exist, signed, qtype=ANY, DO=1, unless break-dnssec is enabled" + ) + if break_dnssec: + if ftype == "a": + expected4 = None + else: + expected6 = None + check_any(addr, addr, "dual.signed", expected4, expected6, True) + else: + check_any(addr, addr, "dual.signed", "1.0.0.3", "2001:db8::3", True) + + expected4 = "1.0.0.6" if ftype == "aaaa" else None + expected6 = "2001:db8::6" if ftype == "a" else None + + isctest.log.debug( + f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, unsigned, qtype=ANY, DO=0" + ) + check_any(addr, addr, "dual.unsigned", expected4, expected6, False) + + isctest.log.debug( + f"check that A/AAAA (and not {qtype}) is returned if both AAAA and A exist, unsigned, qtype=ANY, DO=1" + ) + check_any(addr, addr, "dual.unsigned", expected4, expected6, True) + + isctest.log.debug( + "check that both A and AAAA are returned if both AAAA and A exist, signed, qtype=ANY, query source does not match ACL" + ) + check_any(addr, altaddr, "dual.unsigned", "1.0.0.6", "2001:db8::6", True) + + isctest.log.debug( + f"check that {qtype} is omitted from additional section, qtype=NS, unsigned" + ) + check_additional(addr, addr, "unsigned", "ns", ftype, False, 1) + + isctest.log.debug( + f"check that {qtype} is omitted from additional section, qtype=MX, unsigned" + ) + check_additional(addr, addr, "unsigned", "mx", ftype, False, 2) + + isctest.log.debug( + f"check that {qtype} is included in additional section, qtype=MX, signed, unless break-dnssec is enabled" + ) + if break_dnssec: + check_additional(addr, addr, "signed", "mx", ftype, False, 4) + else: + check_additional(addr, addr, "signed", "mx", ftype, True, 8) + + +def check_filter_other_family(addr, ftype): + isctest.log.debug( + "check that the filtered type is returned when both AAAA and A record exists, unsigned, over other family" + ) + check_filtertype_only(addr, addr, "dual.unsigned", ftype, None, False) + + isctest.log.debug( + "check that the filtered type is included in additional section, qtype=MX, unsigned, over other family" + ) + check_additional(addr, addr, "unsigned", "mx", ftype, True, 4) diff --git a/bin/tests/system/filters/ns2/named.conf.j2 b/bin/tests/system/filters/ns2/named.conf.j2 index 9fee67291c..e02dd97cc7 100644 --- a/bin/tests/system/filters/ns2/named.conf.j2 +++ b/bin/tests/system/filters/ns2/named.conf.j2 @@ -16,6 +16,7 @@ options { query-source address 10.53.0.2; + query-source-v6 address fd92:7065:b8e:ffff::2; notify-source 10.53.0.2; transfer-source 10.53.0.2; port @PORT@; diff --git a/bin/tests/system/filters/ns3/named.conf.j2 b/bin/tests/system/filters/ns3/named.conf.j2 index d89f96df2c..286fe0cdc4 100644 --- a/bin/tests/system/filters/ns3/named.conf.j2 +++ b/bin/tests/system/filters/ns3/named.conf.j2 @@ -16,6 +16,7 @@ options { query-source address 10.53.0.3; + query-source-v6 address fd92:7065:b8e:ffff::3; notify-source 10.53.0.3; transfer-source 10.53.0.3; port @PORT@; diff --git a/bin/tests/system/filters/ns5/named.conf.j2 b/bin/tests/system/filters/ns5/named.conf.j2 index ae72eb33c4..58acd797ea 100644 --- a/bin/tests/system/filters/ns5/named.conf.j2 +++ b/bin/tests/system/filters/ns5/named.conf.j2 @@ -11,8 +11,6 @@ * information regarding copyright ownership. */ -{% set filtertype = filtertype | default("aaaa") %} - options { query-source address 10.53.0.5; notify-source 10.53.0.5; @@ -32,9 +30,9 @@ options { minimal-responses no; }; -plugin query "@TOP_BUILDDIR@/filter-@filtertype@.@DYLIB@" { - filter-@filtertype@-on-v4 break-dnssec; - filter-@filtertype@ { any; }; +plugin query "@TOP_BUILDDIR@/filter-aaaa.@DYLIB@" { + filter-aaaa-on-v4 break-dnssec; + filter-aaaa { any; }; }; key rndc_key { diff --git a/bin/tests/system/filters/tests_filter_a_v4.py b/bin/tests/system/filters/tests_filter_a_v4.py new file mode 100644 index 0000000000..e5aea1bcfb --- /dev/null +++ b/bin/tests/system/filters/tests_filter_a_v4.py @@ -0,0 +1,60 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +import isctest.mark + +from filters.common import ( + ARTIFACTS, + check_filter, + check_filter_other_family, + prime_cache, + reconfigure_servers, +) + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +@pytest.fixture(scope="module", autouse=True) +def setup_filters(servers, templates): + isctest.log.info("configuring server to filter A on V4") + reconfigure_servers("a", "v4", servers, templates) + prime_cache("10.53.0.2") + prime_cache("10.53.0.3") + + +@pytest.mark.parametrize( + "addr, altaddr, break_dnssec, recursive", + [ + pytest.param("10.53.0.1", "10.53.0.2", False, False, id="auth"), + pytest.param("10.53.0.4", "10.53.0.2", True, False, id="auth-break-dnssec"), + pytest.param("10.53.0.2", "10.53.0.1", False, True, id="recurs"), + pytest.param("10.53.0.3", "10.53.0.1", True, True, id="recurs-break-dnssec"), + ], +) +def test_filter_a_on_v4(addr, altaddr, break_dnssec, recursive): + check_filter(addr, altaddr, "a", break_dnssec, recursive) + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "addr", + [ + pytest.param("fd92:7065:b8e:ffff::1", id="auth"), + pytest.param("fd92:7065:b8e:ffff::4", id="auth-break-dnssec"), + pytest.param("fd92:7065:b8e:ffff::2", id="recurs"), + pytest.param("fd92:7065:b8e:ffff::3", id="recurs-break-dnssec"), + ], +) +def test_filter_a_on_v4_via_v6(addr): + check_filter_other_family(addr, "a") diff --git a/bin/tests/system/filters/tests_filter_a_v6.py b/bin/tests/system/filters/tests_filter_a_v6.py new file mode 100644 index 0000000000..17e14e8b34 --- /dev/null +++ b/bin/tests/system/filters/tests_filter_a_v6.py @@ -0,0 +1,80 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +import isctest.mark + +from filters.common import ( + ARTIFACTS, + check_filter, + check_filter_other_family, + prime_cache, + reconfigure_servers, +) + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +@pytest.fixture(scope="module", autouse=True) +def setup_filters(servers, templates): + isctest.log.info("configuring server to filter A on V6") + reconfigure_servers("a", "v6", servers, templates) + prime_cache("fd92:7065:b8e:ffff::2") + prime_cache("fd92:7065:b8e:ffff::3") + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "addr, altaddr, break_dnssec, recursive", + [ + pytest.param( + "fd92:7065:b8e:ffff::1", "fd92:7065:b8e:ffff::2", False, False, id="auth" + ), + pytest.param( + "fd92:7065:b8e:ffff::4", + "fd92:7065:b8e:ffff::2", + True, + False, + id="auth-break-dnssec", + ), + pytest.param( + "fd92:7065:b8e:ffff::2", + "fd92:7065:b8e:ffff::1", + False, + True, + id="recurs", + ), + pytest.param( + "fd92:7065:b8e:ffff::3", + "fd92:7065:b8e:ffff::1", + True, + True, + id="recurs-break-dnssec", + ), + ], +) +def test_filter_a_on_v6(addr, altaddr, break_dnssec, recursive): + check_filter(addr, altaddr, "a", break_dnssec, recursive) + + +@pytest.mark.parametrize( + "addr", + [ + pytest.param("10.53.0.1", id="auth"), + pytest.param("10.53.0.4", id="auth-break-dnssec"), + pytest.param("10.53.0.2", id="recurs"), + pytest.param("10.53.0.3", id="recurs-break-dnssec"), + ], +) +def test_filter_a_on_v6_via_v4(addr): + check_filter_other_family(addr, "a") diff --git a/bin/tests/system/filters/tests_filter_aaaa_v4.py b/bin/tests/system/filters/tests_filter_aaaa_v4.py new file mode 100644 index 0000000000..90aac2a0cd --- /dev/null +++ b/bin/tests/system/filters/tests_filter_aaaa_v4.py @@ -0,0 +1,61 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +import isctest +import isctest.mark + +from filters.common import ( + ARTIFACTS, + check_filter, + check_filter_other_family, + prime_cache, + reconfigure_servers, +) + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +@pytest.fixture(scope="module", autouse=True) +def setup_filters(servers, templates): + isctest.log.info("configuring server to filter AAAA on V4") + reconfigure_servers("aaaa", "v4", servers, templates) + prime_cache("10.53.0.2") + prime_cache("10.53.0.3") + + +@pytest.mark.parametrize( + "addr, altaddr, break_dnssec, recursive", + [ + pytest.param("10.53.0.1", "10.53.0.2", False, False, id="auth"), + pytest.param("10.53.0.4", "10.53.0.2", True, False, id="auth-break-dnssec"), + pytest.param("10.53.0.2", "10.53.0.1", False, True, id="recurs"), + pytest.param("10.53.0.3", "10.53.0.1", True, True, id="recurs-break-dnssec"), + ], +) +def test_filter_aaaa_on_v4(addr, altaddr, break_dnssec, recursive): + check_filter(addr, altaddr, "aaaa", break_dnssec, recursive) + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "addr", + [ + pytest.param("fd92:7065:b8e:ffff::1", id="auth"), + pytest.param("fd92:7065:b8e:ffff::4", id="auth-break-dnssec"), + pytest.param("fd92:7065:b8e:ffff::2", id="recurs"), + pytest.param("fd92:7065:b8e:ffff::3", id="recurs-break-dnssec"), + ], +) +def test_filter_aaaa_on_v4_via_v6(addr): + check_filter_other_family(addr, "aaaa") diff --git a/bin/tests/system/filters/tests_filter_aaaa_v6.py b/bin/tests/system/filters/tests_filter_aaaa_v6.py new file mode 100644 index 0000000000..a439316677 --- /dev/null +++ b/bin/tests/system/filters/tests_filter_aaaa_v6.py @@ -0,0 +1,81 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +import isctest.mark + +from filters.common import ( + ARTIFACTS, + check_filter, + check_filter_other_family, + prime_cache, + reconfigure_servers, +) + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +@pytest.fixture(scope="module", autouse=True) +def setup_filters(servers, templates): + isctest.log.info("configuring server to filter AAAA on V6") + reconfigure_servers("aaaa", "v6", servers, templates) + prime_cache("fd92:7065:b8e:ffff::2") + prime_cache("fd92:7065:b8e:ffff::3") + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "addr, altaddr, break_dnssec, recursive", + [ + pytest.param( + "fd92:7065:b8e:ffff::1", "fd92:7065:b8e:ffff::2", False, False, id="auth" + ), + pytest.param( + "fd92:7065:b8e:ffff::4", + "fd92:7065:b8e:ffff::2", + True, + False, + id="auth-break-dnssec", + ), + pytest.param( + "fd92:7065:b8e:ffff::2", + "fd92:7065:b8e:ffff::1", + False, + True, + id="recurs", + ), + pytest.param( + "fd92:7065:b8e:ffff::3", + "fd92:7065:b8e:ffff::1", + True, + True, + id="recurs-break-dnssec", + ), + ], +) +def test_filter_aaaa_on_v6(addr, altaddr, break_dnssec, recursive): + check_filter(addr, altaddr, "aaaa", break_dnssec, recursive) + + +@isctest.mark.with_ipv6 +@pytest.mark.parametrize( + "addr", + [ + pytest.param("10.53.0.1", id="auth"), + pytest.param("10.53.0.4", id="auth-break-dnssec"), + pytest.param("10.53.0.2", id="recurs"), + pytest.param("10.53.0.3", id="recurs-break-dnssec"), + ], +) +def test_filter_aaaa_on_v6_via_v4(addr): + check_filter_other_family(addr, "aaaa") diff --git a/bin/tests/system/filters/tests_filter_checkconf.py b/bin/tests/system/filters/tests_filter_checkconf.py new file mode 100644 index 0000000000..17dbdb989a --- /dev/null +++ b/bin/tests/system/filters/tests_filter_checkconf.py @@ -0,0 +1,32 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import glob +import os +import subprocess + +import pytest + +import isctest + +from filters.common import ARTIFACTS + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +# FUTURE: move this to checkconf test - it doesn't need nsX servers +def test_filters_checkconf(): + for filename in glob.glob("conf/good*.conf"): + isctest.run.cmd([os.environ["CHECKCONF"], filename]) + for filename in glob.glob("conf/bad*.conf"): + with pytest.raises(subprocess.CalledProcessError): + isctest.run.cmd([os.environ["CHECKCONF"], filename]) diff --git a/bin/tests/system/filters/tests_filter_dns64.py b/bin/tests/system/filters/tests_filter_dns64.py new file mode 100644 index 0000000000..218b834682 --- /dev/null +++ b/bin/tests/system/filters/tests_filter_dns64.py @@ -0,0 +1,28 @@ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# SPDX-License-Identifier: MPL-2.0 +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at https://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +import pytest + +import isctest + +from filters.common import ARTIFACTS + + +pytestmark = pytest.mark.extra_artifacts(ARTIFACTS) + + +def test_filter_dns64(): + # This configuration doesn't make sense. The AAAA is wanted by + # filter-aaaa, but discarded by the dns64 configuration. We just + # need to ensure that the server keeps running. + msg = isctest.query.create("aaaa-only.unsigned", "aaaa") + res = isctest.query.tcp(msg, "10.53.0.5") + isctest.check.noerror(res) diff --git a/bin/tests/system/filters/tests_filters.py b/bin/tests/system/filters/tests_filters.py deleted file mode 100644 index 58149973ae..0000000000 --- a/bin/tests/system/filters/tests_filters.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/bin/sh - -# Copyright (C) Internet Systems Consortium, Inc. ("ISC") -# -# SPDX-License-Identifier: MPL-2.0 -# -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, you can obtain one at https://mozilla.org/MPL/2.0/. -# -# See the COPYRIGHT file distributed with this work for additional -# information regarding copyright ownership. - -import glob -import os -import subprocess - -import dns -from dns import message, rdataclass, rdatatype - -import pytest - -import isctest -import isctest.mark - - -pytestmark = pytest.mark.extra_artifacts( - [ - "conf/*.conf", - "ns*/trusted.conf", - "ns*/*.signed", - "ns*/K*", - "ns*/dsset-*", - "ns*/signer.err", - ] -) - - -# helper functions -def reset_server(server, family, ftype, servers, templates): - templates.render(f"{server}/named.conf", {"family": family, "filtertype": ftype}) - servers[server].reconfigure(log=False) - - -# these are the default configuration values for the jinja2 -# templates. if some other value is needed for a test, then -# the named.conf files must be regenerated. -filter_family = "v4" -filter_type = "aaaa" - - -def reset_servers(family, ftype, servers, templates): - reset_server("ns1", family, ftype, servers, templates) - reset_server("ns2", family, ftype, servers, templates) - reset_server("ns3", family, ftype, servers, templates) - reset_server("ns4", family, ftype, servers, templates) - - -def check_filtertype_only(dest, source, qname, ftype, expected, adflag): - qname = dns.name.from_text(qname) - msg = isctest.query.create(qname, ftype) - res = isctest.query.tcp(msg, dest, source=source) - isctest.check.noerror(res) - if adflag: - isctest.check.adflag(res) - else: - isctest.check.noadflag(res) - a_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.A) - aaaa_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.AAAA) - if ftype == "aaaa": - assert not a_record - if expected: - assert ( - aaaa_record[0].address == expected - ), f"expected AAAA {expected} in ANSWER: {res}" - else: - assert not aaaa_record - if expected: - assert ( - a_record[0].address == expected - ), f"expected A {expected} in ANSWER: {res}" - - -def check_any(dest, source, qname, expected4, expected6, do): - qname = dns.name.from_text(qname) - msg = isctest.query.create(qname, "any", dnssec=do) - res = isctest.query.tcp(msg, dest, source=source) - isctest.check.noerror(res) - a_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.A) - if expected4: - assert ( - a_record and a_record[0].address == expected4 - ), f"expected A {expected4} in ANSWER: {res}" - else: - assert not a_record - aaaa_record = res.get_rrset(res.answer, qname, rdataclass.IN, rdatatype.AAAA) - if expected6: - assert ( - aaaa_record and aaaa_record[0].address == expected6 - ), f"expected AAAA {expected6} in ANSWER: {res}" - else: - assert not aaaa_record - - -def check_nodata(dest, source, qname, qtype, do, adflag): - msg = isctest.query.create(qname, qtype, dnssec=do) - res = isctest.query.tcp(msg, dest, source=source) - isctest.check.noerror(res) - isctest.check.empty_answer(res) - if adflag: - isctest.check.adflag(res) - else: - isctest.check.noadflag(res) - - -def check_additional(dest, source, qname, qtype, ftype, expected, adcount): - msg = isctest.query.create(qname, qtype) - res = isctest.query.tcp(msg, dest, source=source) - isctest.check.noerror(res) - isctest.check.rr_count_eq(res.additional, adcount) - t = rdatatype.A if ftype == "a" else rdatatype.AAAA - if expected: - assert [a for a in res.additional if a.rdtype == t] - else: - assert not [a for a in res.additional if a.rdtype == t] - - -# run the checkconf tests -def test_checkconf(): - for filename in glob.glob("conf/good*.conf"): - isctest.run.cmd([os.environ["CHECKCONF"], filename]) - for filename in glob.glob("conf/bad*.conf"): - with pytest.raises(subprocess.CalledProcessError): - isctest.run.cmd([os.environ["CHECKCONF"], filename]) - - -def check_filter(addr, altaddr, ftype, break_dnssec, recursive): - if recursive: - # (when testing recursive, we need to prime the cache first with - # the MX addresses, since additional section data isn't included - # unless it's been validated.) - for name in ["mx", "ns"]: - for zone in ["signed", "unsigned"]: - for qtype in ["a", "aaaa"]: - isctest.query.tcp( - isctest.query.create(f"{name}.{zone}", qtype), addr - ) - - # check that AAAA is returned when only AAAA record exists, signed - expected = "1.0.0.2" if ftype == "a" else "2001:db8::2" - check_filtertype_only( - addr, addr, f"{ftype}-only.signed", ftype, expected, recursive - ) - - # check that AAAA is returned when only AAAA record exists, unsigned - expected = "1.0.0.5" if ftype == "a" else "2001:db8::5" - check_filtertype_only(addr, addr, f"{ftype}-only.unsigned", ftype, expected, False) - - # check that NODATA/NOERROR is returned when both AAAA and A exist, - # signed, DO=0 - check_nodata(addr, addr, "dual.signed", ftype, False, False) - - # check that NODATA/NOERROR is returned when both AAAA and A exist, - # unsigned, DO=0 - check_nodata(addr, addr, "dual.unsigned", ftype, False, False) - - # check that AAAA is returned when both AAAA and A exist, signed, - # DO=1, unless break-dnssec is enabled - if break_dnssec: - check_nodata(addr, addr, "dual.signed", ftype, False, False) - else: - expected = "1.0.0.3" if ftype == "a" else "2001:db8::3" - check_filtertype_only(addr, addr, "dual.signed", ftype, expected, recursive) - - # check that NODATA/NOERROR is returned when both AAAA and A exist, - # unsigned, DO=1 - check_nodata(addr, addr, "dual.unsigned", ftype, recursive, False) - - # check that AAAA is returned if both AAAA and A exist and the query - # source doesn't match the ACL - expected = "1.0.0.6" if ftype == "a" else "2001:db8::6" - check_filtertype_only(addr, altaddr, "dual.unsigned", ftype, expected, False) - - # check that A (and not AAAA) is returned if both AAAA and A exist, - # signed, qtype=ANY, DO=0 - expected4 = "1.0.0.3" if ftype == "aaaa" else None - expected6 = "2001:db8::3" if ftype == "a" else None - check_any(addr, addr, "dual.signed", expected4, expected6, False) - - # check that both A and AAAA are returned if both AAAA and A exist, - # signed, qtype=ANY, DO=1, unless break-dnssec is enabled - if break_dnssec: - if ftype == "a": - expected4 = None - else: - expected6 = None - check_any(addr, addr, "dual.signed", expected4, expected6, True) - else: - check_any(addr, addr, "dual.signed", "1.0.0.3", "2001:db8::3", True) - - expected4 = "1.0.0.6" if ftype == "aaaa" else None - expected6 = "2001:db8::6" if ftype == "a" else None - # check that A (and not AAAA) is returned if both AAAA and A exist, - # unsigned, qtype=ANY, DO=0 - check_any(addr, addr, "dual.unsigned", expected4, expected6, False) - - # check that A (and not AAAA) is returned if both AAAA and A exist, - # unsigned, qtype=ANY, DO=1 - check_any(addr, addr, "dual.unsigned", expected4, expected6, True) - - # check that both A and AAAA are returned if both AAAA and A exist, - # signed, qtype=ANY, query source does not match ACL - check_any(addr, altaddr, "dual.unsigned", "1.0.0.6", "2001:db8::6", True) - - # check that AAAA is omitted from additional section, qtype=NS, unsigned - check_additional(addr, addr, "unsigned", "ns", ftype, False, 1) - - # check that AAAA is omitted from additional section, qtype=MX, unsigned - check_additional(addr, addr, "unsigned", "mx", ftype, False, 2) - - # check that AAAA is included in additional section, qtype=MX, signed, - # unless break-dnssec is enabled - if break_dnssec: - check_additional(addr, addr, "signed", "mx", ftype, False, 4) - else: - check_additional(addr, addr, "signed", "mx", ftype, True, 8) - - -def check_filter_other_family(addr, ftype): - # check that the filtered type is returned when both AAAA and A - # record exists, unsigned, over IPv6 - check_filtertype_only(addr, addr, "dual.unsigned", ftype, None, False) - - # check that the filtered type is included in additional section, - # qtype=MX, unsigned, over IPv6 - check_additional(addr, addr, "unsigned", "mx", ftype, True, 4) - - -def test_filter_aaaa_on_v4(servers, templates): - if filter_family != "v4" or filter_type != "aaaa": - reset_servers("v4", "aaaa", servers, templates) - - # ns1: auth, configured with: - ## filter-aaaa-on-v4 yes; - ## filter-aaaa { 10.53.0.1; }; - check_filter("10.53.0.1", "10.53.0.2", "aaaa", False, False) - - # ns4: auth, configured with: - ## filter-aaaa-on-v4 break-dnssec; - ## filter-aaaa { 10.53.0.4; }; - check_filter("10.53.0.4", "10.53.0.2", "aaaa", True, False) - - # ns2: recursive, configured with: - ## filter-aaaa-on-v4 yes; - ## filter-aaaa { 10.53.0.2; }; - check_filter("10.53.0.2", "10.53.0.1", "aaaa", False, True) - - # ns3: recursive, configured with: - ## filter-aaaa-on-v4 break-dnssec; - ## filter-aaaa { 10.53.0.3; }; - check_filter("10.53.0.3", "10.53.0.1", "aaaa", True, True) - - -@isctest.mark.with_ipv6 -def test_filter_aaaa_on_v4_via_v6(servers, templates): - if filter_family != "v4" or filter_type != "aaaa": - reset_servers("v4", "aaaa", servers, templates) - - check_filter_other_family("fd92:7065:b8e:ffff::1", "aaaa") - check_filter_other_family("fd92:7065:b8e:ffff::2", "aaaa") - check_filter_other_family("fd92:7065:b8e:ffff::3", "aaaa") - check_filter_other_family("fd92:7065:b8e:ffff::4", "aaaa") - - -# These tests are against an authoritative server configured with: -## filter-aaaa-on-v6 yes; -@isctest.mark.with_ipv6 -def test_filter_aaaa_on_v6(servers, templates): - if filter_family != "v6" or filter_type != "aaaa": - reset_servers("v6", "aaaa", servers, templates) - - # ns1: auth, configured with: - ## filter-aaaa-on-v6 yes; - ## filter-aaaa { fd92:7065:b8e:ffff::1; }; - check_filter("fd92:7065:b8e:ffff::1", "fd92:7065:b8e:ffff::2", "aaaa", False, False) - - # ns4: auth, configured with: - ## filter-aaaa-on-v6 break-dnssec; - ## filter-aaaa { fd92:7065:b8e:ffff::4; }; - check_filter("fd92:7065:b8e:ffff::4", "fd92:7065:b8e:ffff::2", "aaaa", True, False) - - # ns2: recursive, configured with: - ## filter-aaaa-on-v6 yes; - ## filter-aaaa { fd92:7065:b8e:ffff::2; }; - check_filter("fd92:7065:b8e:ffff::2", "fd92:7065:b8e:ffff::1", "aaaa", False, True) - - # ns3: recursive, configured with: - ## filter-aaaa-on-v6 break-dnssec; - ## filter-aaaa { fd92:7065:b8e:ffff::3; }; - check_filter("fd92:7065:b8e:ffff::3", "fd92:7065:b8e:ffff::1", "aaaa", True, True) - - -def test_filter_aaaa_on_v6_via_v4(servers, templates): - if filter_family != "v6" or filter_type != "aaaa": - reset_servers("v6", "aaaa", servers, templates) - - check_filter_other_family("10.53.0.1", "aaaa") - check_filter_other_family("10.53.0.2", "aaaa") - check_filter_other_family("10.53.0.3", "aaaa") - check_filter_other_family("10.53.0.4", "aaaa") - - -def test_filter_a_on_v4(servers, templates): - if filter_family != "v4" or filter_type != "a": - reset_servers("v4", "a", servers, templates) - - # ns1: auth, configured with: - ## filter-a-on-v4 yes; - ## filter-a { 10.53.0.1; }; - check_filter("10.53.0.1", "10.53.0.2", "a", False, False) - - # ns4: auth, configured with: - ## filter-a-on-v4 break-dnssec; - ## filter-a { 10.53.0.4; }; - check_filter("10.53.0.4", "10.53.0.2", "a", True, False) - - # ns2: recursive, configured with: - ## filter-a-on-v4 yes; - ## filter-a { 10.53.0.2; }; - check_filter("10.53.0.2", "10.53.0.1", "a", False, True) - - # ns3: recursive, configured with: - ## filter-a-on-v4 break-dnssec; - ## filter-a { 10.53.0.3; }; - check_filter("10.53.0.3", "10.53.0.1", "a", True, True) - - -@isctest.mark.with_ipv6 -def test_filter_a_on_v4_via_v6(servers, templates): - if filter_family != "v4" or filter_type != "a": - reset_servers("v4", "a", servers, templates) - - check_filter_other_family("fd92:7065:b8e:ffff::1", "a") - check_filter_other_family("fd92:7065:b8e:ffff::2", "a") - check_filter_other_family("fd92:7065:b8e:ffff::3", "a") - check_filter_other_family("fd92:7065:b8e:ffff::4", "a") - - -# These tests are against an authoritative server configured with: -## filter-a-on-v6 yes; -@isctest.mark.with_ipv6 -def test_filter_a_on_v6(servers, templates): - if filter_family != "v6" or filter_type != "a": - reset_servers("v6", "a", servers, templates) - - # ns1: auth, configured with: - ## filter-a-on-v6 yes; - ## filter-a { fd92:7065:b8e:ffff::1; }; - check_filter("fd92:7065:b8e:ffff::1", "fd92:7065:b8e:ffff::2", "a", False, False) - - # ns4: auth, configured with: - ## filter-a-on-v6 break-dnssec; - ## filter-a { fd92:7065:b8e:ffff::4; }; - check_filter("fd92:7065:b8e:ffff::4", "fd92:7065:b8e:ffff::2", "a", True, False) - - # ns2: recursive, configured with: - ## filter-a-on-v6 yes; - ## filter-a { fd92:7065:b8e:ffff::2; }; - check_filter("fd92:7065:b8e:ffff::2", "fd92:7065:b8e:ffff::1", "a", False, True) - - # ns3: recursive, configured with: - ## filter-a-on-v6 break-dnssec; - ## filter-a { fd92:7065:b8e:ffff::3; }; - check_filter("fd92:7065:b8e:ffff::3", "fd92:7065:b8e:ffff::1", "a", True, True) - - -def test_filter_a_on_v6_via_v4(servers, templates): - if filter_family != "v6" or filter_type != "a": - reset_servers("v6", "a", servers, templates) - - check_filter_other_family("10.53.0.1", "a") - check_filter_other_family("10.53.0.2", "a") - check_filter_other_family("10.53.0.3", "a") - check_filter_other_family("10.53.0.4", "a")