]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
add tests for per-zone hook instance support
authorColin Vidal <colin@isc.org>
Tue, 27 May 2025 12:20:02 +0000 (14:20 +0200)
committerColin Vidal <colin@isc.org>
Tue, 9 Sep 2025 07:42:34 +0000 (09:42 +0200)
add system tests covering the individual support of zone plugins.

15 files changed:
bin/tests/system/hooks/conf/bad-topviewlevel.conf [deleted file]
bin/tests/system/hooks/conf/bad-topviewlevel.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/conf/good-toplevel.conf [deleted file]
bin/tests/system/hooks/conf/good-toplevel.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/conf/good-viewlevel.conf [deleted file]
bin/tests/system/hooks/conf/good-viewlevel.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/conf/good-viewzonelevel.conf [deleted file]
bin/tests/system/hooks/conf/good-viewzonelevel.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/conf/good-zonelevel.conf [deleted file]
bin/tests/system/hooks/conf/good-zonelevel.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/driver/test-syncplugin.c
bin/tests/system/hooks/ns2/example.db [new file with mode: 0644]
bin/tests/system/hooks/ns2/example4.com.db [new file with mode: 0644]
bin/tests/system/hooks/ns2/named.conf.j2 [new file with mode: 0644]
bin/tests/system/hooks/tests_hooks.py

diff --git a/bin/tests/system/hooks/conf/bad-topviewlevel.conf b/bin/tests/system/hooks/conf/bad-topviewlevel.conf
deleted file mode 100644 (file)
index b5b267a..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-view foo {
-       plugin query "driver/.libs/test-syncplugin.so";
-};
-plugin query "driver/.libs/test-syncplugin.so";
diff --git a/bin/tests/system/hooks/conf/bad-topviewlevel.conf.j2 b/bin/tests/system/hooks/conf/bad-topviewlevel.conf.j2
new file mode 100644 (file)
index 0000000..9e7338d
--- /dev/null
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+view foo {
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
+};
+plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
diff --git a/bin/tests/system/hooks/conf/good-toplevel.conf b/bin/tests/system/hooks/conf/good-toplevel.conf
deleted file mode 100644 (file)
index ebfd44a..0000000
+++ /dev/null
@@ -1 +0,0 @@
-plugin query "driver/.libs/test-syncplugin.so";
diff --git a/bin/tests/system/hooks/conf/good-toplevel.conf.j2 b/bin/tests/system/hooks/conf/good-toplevel.conf.j2
new file mode 100644 (file)
index 0000000..3e416a6
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * 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.
+ */
+
+plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
diff --git a/bin/tests/system/hooks/conf/good-viewlevel.conf b/bin/tests/system/hooks/conf/good-viewlevel.conf
deleted file mode 100644 (file)
index 24668bd..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-view foo {
-       plugin query "driver/.libs/test-syncplugin.so";
-};
diff --git a/bin/tests/system/hooks/conf/good-viewlevel.conf.j2 b/bin/tests/system/hooks/conf/good-viewlevel.conf.j2
new file mode 100644 (file)
index 0000000..7efc0a1
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+view foo {
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
+};
diff --git a/bin/tests/system/hooks/conf/good-viewzonelevel.conf b/bin/tests/system/hooks/conf/good-viewzonelevel.conf
deleted file mode 100644 (file)
index 41d4d4e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-view someview {
-       zone "foo.bar" {
-               plugin query "driver/.libs/test-syncplugin.so";
-               type primary;
-               file "foo.bar.db";
-       };
-       plugin query "driver/.libs/test-syncplugin.so";
-};
diff --git a/bin/tests/system/hooks/conf/good-viewzonelevel.conf.j2 b/bin/tests/system/hooks/conf/good-viewzonelevel.conf.j2
new file mode 100644 (file)
index 0000000..a1b0f2c
--- /dev/null
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+view someview {
+       zone "foo.bar" {
+               plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
+               type primary;
+               file "foo.bar.db";
+       };
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
+};
diff --git a/bin/tests/system/hooks/conf/good-zonelevel.conf b/bin/tests/system/hooks/conf/good-zonelevel.conf
deleted file mode 100644 (file)
index d4e546b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-zone "foo.bar" {
-       plugin query "driver/.libs/test-syncplugin.so";
-       type primary;
-       file "foo.bar.db";
-};
diff --git a/bin/tests/system/hooks/conf/good-zonelevel.conf.j2 b/bin/tests/system/hooks/conf/good-zonelevel.conf.j2
new file mode 100644 (file)
index 0000000..133ef86
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * 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.
+ */
+
+zone "foo.bar" {
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" { rcode servfail; };
+       type primary;
+       file "foo.bar.db";
+};
index 52fd1835fcc2ea1d49f1b78e7705bfb709861e11..1d082900b66a00ad5b8225847e7337beac191c4d 100644 (file)
 
 #include <ns/hooks.h>
 
+typedef struct {
+       isc_mem_t *mctx;
+       uint8_t rcode;
+
+       /*
+        * Plugin will bails out without altering the response if qname first
+        * label matches `firstlbl`.
+        */
+       char *firstlbl;
+} syncplugin_t;
+
+#define CHECK(op)                              \
+       do {                                   \
+               result = (op);                 \
+               if (result != ISC_R_SUCCESS) { \
+                       goto cleanup;          \
+               }                              \
+       } while (0)
+
 static ns_hookresult_t
-syncplugin_hook(void *arg, void *cbdata, isc_result_t *resp) {
-       UNUSED(arg);
-       UNUSED(cbdata);
+syncplugin__hook(void *arg, void *cbdata, isc_result_t *resp) {
+       query_ctx_t *qctx = (query_ctx_t *)arg;
+       syncplugin_t *inst = cbdata;
+
        UNUSED(resp);
 
-       return NS_HOOK_CONTINUE;
+       if (inst->firstlbl != NULL) {
+               const dns_name_t *qname = qctx->client->query.qname;
+               dns_label_t label;
+
+               dns_name_getlabel(qname, 0, &label);
+
+               /*
+                * +1 because the first label byte is the length of the label
+                * itself
+                */
+               if (strncmp(inst->firstlbl, (char *)label.base + 1,
+                           strlen(inst->firstlbl)) == 0)
+               {
+                       return NS_HOOK_CONTINUE;
+               }
+       }
+
+       qctx->client->message->rcode = inst->rcode;
+       *resp = ns_query_done(qctx);
+       return NS_HOOK_RETURN;
+}
+
+static cfg_clausedef_t syncplugin__cfgclauses[] = {
+       { "rcode", &cfg_type_astring, 0 },
+       { "firstlbl", &cfg_type_qstring, CFG_CLAUSEFLAG_OPTIONAL }
+};
+
+static cfg_clausedef_t *syncplugin__cfgparamsclausesets[] = {
+       syncplugin__cfgclauses, NULL
+};
+
+static cfg_type_t syncplugin__cfgparams = {
+       "syncplugin-params", cfg_parse_mapbody, cfg_print_mapbody,
+       cfg_doc_mapbody,     &cfg_rep_map,      syncplugin__cfgparamsclausesets
+};
+
+static isc_result_t
+syncplugin__parse_rcode(const cfg_obj_t *syncplugincfg, uint8_t *rcode) {
+       isc_result_t result;
+       const cfg_obj_t *obj = NULL;
+       const char *rcodestr = NULL;
+
+       result = cfg_map_get(syncplugincfg, "rcode", &obj);
+       if (result != ISC_R_SUCCESS) {
+               return result;
+       }
+
+       rcodestr = obj->value.string.base;
+
+       if (strcmp("servfail", rcodestr) == 0) {
+               *rcode = dns_rcode_servfail;
+       } else if (strcmp("notimp", rcodestr) == 0) {
+               *rcode = dns_rcode_notimp;
+       } else if (strcmp("noerror", rcodestr) == 0) {
+               *rcode = dns_rcode_noerror;
+       } else if (strcmp("notauth", rcodestr) == 0) {
+               *rcode = dns_rcode_notauth;
+       } else if (strcmp("notzone", rcodestr) == 0) {
+               *rcode = dns_rcode_notzone;
+       } else {
+               result = ISC_R_FAILURE;
+       }
+
+       return result;
 }
 
 isc_result_t
 plugin_register(const char *parameters, const void *cfg, const char *cfgfile,
                unsigned long cfgline, isc_mem_t *mctx, void *actx,
                ns_hooktable_t *hooktable, void **instp) {
+       isc_result_t result;
+       cfg_parser_t *parser = NULL;
+       cfg_obj_t *syncplugincfg = NULL;
+       const cfg_obj_t *obj = NULL;
+       isc_buffer_t b;
        ns_hook_t hook;
+       syncplugin_t *inst = NULL;
 
-       UNUSED(parameters);
        UNUSED(cfg);
-       UNUSED(cfgfile);
-       UNUSED(cfgline);
-       UNUSED(mctx);
        UNUSED(actx);
-       UNUSED(hooktable);
-       UNUSED(instp);
 
-       hook = (ns_hook_t){ .action = syncplugin_hook,
-                           .action_data = NULL };
-       ns_hook_add(hooktable, mctx, NS_QUERY_NODATA_BEGIN, &hook);
+       inst = isc_mem_get(mctx, sizeof(*inst));
+       *inst = (syncplugin_t){ .mctx = mctx };
+       *instp = inst;
 
-       return ISC_R_SUCCESS;
+       CHECK(cfg_parser_create(mctx, &parser));
+
+       isc_buffer_constinit(&b, parameters, strlen(parameters));
+       isc_buffer_add(&b, strlen(parameters));
+
+       CHECK(cfg_parse_buffer(parser, &b, cfgfile, cfgline,
+                              &syncplugin__cfgparams, 0, &syncplugincfg));
+
+       CHECK(syncplugin__parse_rcode(syncplugincfg, &inst->rcode));
+
+       if (cfg_map_get(syncplugincfg, "firstlbl", &obj) == ISC_R_SUCCESS) {
+               const char *firstlbl = cfg_obj_asstring(obj);
+               size_t len = strlen(firstlbl) + 1;
+
+               inst->firstlbl = isc_mem_allocate(mctx, len);
+               strncpy(inst->firstlbl, firstlbl, len);
+       }
+
+       hook = (ns_hook_t){ .action = syncplugin__hook, .action_data = inst };
+       ns_hook_add(hooktable, mctx, NS_QUERY_NXDOMAIN_BEGIN, &hook);
+
+cleanup:
+       if (syncplugincfg != NULL) {
+               cfg_obj_destroy(parser, &syncplugincfg);
+       }
+
+       if (parser != NULL) {
+               cfg_parser_destroy(&parser);
+       }
+
+       return result;
 }
 
 isc_result_t
@@ -63,7 +176,15 @@ plugin_check(const char *parameters, const void *cfg, const char *cfgfile,
 
 void
 plugin_destroy(void **instp) {
-       UNUSED(instp);
+       syncplugin_t *inst = *instp;
+       isc_mem_t *mctx = inst->mctx;
+
+       if (inst->firstlbl != NULL) {
+               isc_mem_free(mctx, inst->firstlbl);
+       }
+
+       isc_mem_put(mctx, inst, sizeof(*inst));
+       *instp = NULL;
 }
 
 int
diff --git a/bin/tests/system/hooks/ns2/example.db b/bin/tests/system/hooks/ns2/example.db
new file mode 100644 (file)
index 0000000..20bf604
--- /dev/null
@@ -0,0 +1,21 @@
+; 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.
+
+$TTL   120
+@              SOA     ns.unsigned.  hostmaster.ns.unsigned. ( 1 3600 1200 604800 60 )
+@              NS      ns
+@              MX      10 mx
+
+ns             A       10.53.0.1
+               AAAA    fd92:7065:b8e:ffff::1
+
+a              A       1.1.1.1
+mx             A       2.2.2.2
diff --git a/bin/tests/system/hooks/ns2/example4.com.db b/bin/tests/system/hooks/ns2/example4.com.db
new file mode 100644 (file)
index 0000000..20bf604
--- /dev/null
@@ -0,0 +1,21 @@
+; 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.
+
+$TTL   120
+@              SOA     ns.unsigned.  hostmaster.ns.unsigned. ( 1 3600 1200 604800 60 )
+@              NS      ns
+@              MX      10 mx
+
+ns             A       10.53.0.1
+               AAAA    fd92:7065:b8e:ffff::1
+
+a              A       1.1.1.1
+mx             A       2.2.2.2
diff --git a/bin/tests/system/hooks/ns2/named.conf.j2 b/bin/tests/system/hooks/ns2/named.conf.j2
new file mode 100644 (file)
index 0000000..35431d8
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+options {
+       query-source address 10.53.0.2;
+       notify-source 10.53.0.2;
+       transfer-source 10.53.0.2;
+       port @PORT@;
+       pid-file "named.pid";
+       listen-on { 10.53.0.2; };
+       recursion no;
+       dnssec-validation yes;
+       notify yes;
+       minimal-responses no;
+};
+
+trust-anchors { };
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+       inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" {
+       rcode noerror;
+};
+
+zone "example.com" {
+       type primary;
+       file "example.db";
+};
+
+zone "example2.com" {
+       type primary;
+       file "example.db";
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" {
+               rcode servfail;
+       };
+};
+
+zone "example3.com" {
+       type primary;
+       file "example.db";
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" {
+               rcode notimp;
+       };
+};
+
+template exampletmpl {
+       type primary;
+       file "$name.db";
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" {
+               rcode notauth;
+               firstlbl "skipfoo";
+       };
+};
+
+zone "example4.com" {
+       template exampletmpl;
+       plugin query "@TOP_BUILDDIR@/testlib-driver-syncplugin.so" {
+               rcode notzone;
+       };
+};
index 98c23bf216ec1ddc91eca43768c3d831c4c69cb4..93953d1bdf0b63daeb42e2c0df560b98e507031a 100644 (file)
@@ -9,8 +9,14 @@
 # See the COPYRIGHT file distributed with this work for additional
 # information regarding copyright ownership.
 
+import dns
+import pytest
 import isctest
 
+pytest.importorskip("dns")
+
+pytestmark = pytest.mark.extra_artifacts(["conf/*.conf"])
+
 
 def test_hooks():
     msg = isctest.query.create("example.com.", "A")
@@ -27,5 +33,47 @@ def test_hooks_noextension(ns1, templates):
     test_hooks()
 
 
-def test_plugins_config(run_tests_sh):
+def test_hooks_global_hook():
+    msg = isctest.query.create("idontexists.example.com", "A")
+    res = isctest.query.udp(msg, "10.53.0.2")
+    isctest.check.rcode(res, dns.rcode.NOERROR)
+
+
+def test_hooks_zone_hook1():
+    msg = isctest.query.create("idonotexists.example2.com", "A")
+    res = isctest.query.udp(msg, "10.53.0.2")
+    isctest.check.rcode(res, dns.rcode.SERVFAIL)
+
+
+def test_hooks_zone_hook2():
+    msg = isctest.query.create("idonotexists.example3.com", "A")
+    res = isctest.query.udp(msg, "10.53.0.2")
+    isctest.check.rcode(res, dns.rcode.NOTIMP)
+
+
+# ensure a plugin defined in a template is correcty registered to zone using
+# this template
+def test_hooks_zonetemplate1():
+    msg = isctest.query.create("idonotexists.example4.com", "A")
+    res = isctest.query.udp(msg, "10.53.0.2")
+    isctest.check.rcode(res, dns.rcode.NOTAUTH)
+
+
+# ensure plugin defined in zone are correctly registered when the zone also
+# using a template with plugins (the plugin defined in the template is called
+# first, but it bails out without doing anything because the first label is
+# "skipfoo". So the plugin defined in the zone is then called, and return
+# notzone)
+def test_hooks_zonetemplate2():
+    msg = isctest.query.create("skipfoo.example4.com", "A")
+    res = isctest.query.udp(msg, "10.53.0.2")
+    isctest.check.rcode(res, dns.rcode.NOTZONE)
+
+
+def test_hooks_zone_rndc_reload(servers):
+    ns2 = servers["ns2"]
+    ns2.rndc("reload")
+
+
+def test_hooks_config(run_tests_sh):
     run_tests_sh()