]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Split up and parametrize filters tests
authorNicki Křížek <nicki@isc.org>
Thu, 31 Jul 2025 17:00:27 +0000 (19:00 +0200)
committerEvan Hunt <each@isc.org>
Wed, 6 Aug 2025 06:01:04 +0000 (23:01 -0700)
Move tests which use different configuration to dedicated modules to
avoid possible interference with other tests.

Parametrize the test cases to have a dedicated test for each server
configuration.

Turn the check_filter() comments into log messages to help with
debugging.

12 files changed:
.gitlab-ci.yml
bin/tests/system/filters/common.py [new file with mode: 0644]
bin/tests/system/filters/ns2/named.conf.j2
bin/tests/system/filters/ns3/named.conf.j2
bin/tests/system/filters/ns5/named.conf.j2
bin/tests/system/filters/tests_filter_a_v4.py [new file with mode: 0644]
bin/tests/system/filters/tests_filter_a_v6.py [new file with mode: 0644]
bin/tests/system/filters/tests_filter_aaaa_v4.py [new file with mode: 0644]
bin/tests/system/filters/tests_filter_aaaa_v6.py [new file with mode: 0644]
bin/tests/system/filters/tests_filter_checkconf.py [new file with mode: 0644]
bin/tests/system/filters/tests_filter_dns64.py [new file with mode: 0644]
bin/tests/system/filters/tests_filters.py [deleted file]

index d334ae1ae5b978bdaafd0f861e10722fd477a25e..1c694395b9bc489512134fe997464dbf0b74ce03 100644 (file)
@@ -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 (file)
index 0000000..a009d31
--- /dev/null
@@ -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)
index 9fee67291cafddbb70bc069dc58b2ebc0f66d1ff..e02dd97cc758c6fac81690376d702d5091fe977b 100644 (file)
@@ -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@;
index d89f96df2c546493535c93f54897a1c292d777bc..286fe0cdc4913a7e23b306c8fbcf93a63ae1c91d 100644 (file)
@@ -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@;
index ae72eb33c4ebe48c7206d9e6fa2bcf02a3c5edd3..58acd797eae55b6afa7d8874c29d7185a487af52 100644 (file)
@@ -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 (file)
index 0000000..e5aea1b
--- /dev/null
@@ -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 (file)
index 0000000..17e14e8
--- /dev/null
@@ -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 (file)
index 0000000..90aac2a
--- /dev/null
@@ -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 (file)
index 0000000..a439316
--- /dev/null
@@ -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 (file)
index 0000000..17dbdb9
--- /dev/null
@@ -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 (file)
index 0000000..218b834
--- /dev/null
@@ -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 (file)
index 5814997..0000000
+++ /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")