]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
datamodel: /local-data/subtrees: implement redirect and ttl
authorVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 8 Aug 2023 14:05:51 +0000 (16:05 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 12 Sep 2023 10:12:55 +0000 (12:12 +0200)
Also refactor the C APIs, causing most of the diffs.

12 files changed:
daemon/lua/kres-gen-30.lua
daemon/lua/kres-gen-31.lua
daemon/lua/kres-gen-32.lua
daemon/lua/kres-gen.sh
doc/config-local-data.rst
lib/rules/api.c
lib/rules/api.h
lib/rules/defaults.c
lib/rules/impl.h
lib/rules/zonefile.c
manager/knot_resolver_manager/datamodel/templates/local_data.lua.j2
manager/knot_resolver_manager/datamodel/templates/macros/local_data_macros.lua.j2

index 9d2d846e22511ee8401dc27ba099f480b5461e06..c10a6ae3b6339d3a80bd55307a13c71844ef96e0 100644 (file)
@@ -347,10 +347,11 @@ struct kr_query_data_src {
        kr_rule_fwd_flags_t flags;
        knot_db_val_t targets_ptr;
 };
-
+enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
+const uint32_t KR_RULE_TTL_DEFAULT;
 
 typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type,
                                const struct kr_query *qry);
@@ -494,8 +495,7 @@ int kr_rules_init(const char *, size_t);
 int kr_view_insert_action(const char *, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
-int kr_rule_local_data_emptyzone(const knot_dname_t *, kr_rule_tags_t);
-int kr_rule_local_data_nxdomain(const knot_dname_t *, kr_rule_tags_t);
+int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
 int kr_rule_zonefile(const struct kr_rule_zonefile_config *);
 int kr_rule_forward(const knot_dname_t *, kr_rule_fwd_flags_t, const struct sockaddr **);
 int kr_rule_local_address(const char *, const char *, _Bool, uint32_t, kr_rule_tags_t);
index f10049301b92d8ef62ad31f91b8b6671fbf92156..821c60688d1989f49e501f004f9640372469cac5 100644 (file)
@@ -347,10 +347,11 @@ struct kr_query_data_src {
        kr_rule_fwd_flags_t flags;
        knot_db_val_t targets_ptr;
 };
-
+enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
+const uint32_t KR_RULE_TTL_DEFAULT;
 
 typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type,
                                const struct kr_query *qry);
@@ -494,8 +495,7 @@ int kr_rules_init(const char *, size_t);
 int kr_view_insert_action(const char *, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
-int kr_rule_local_data_emptyzone(const knot_dname_t *, kr_rule_tags_t);
-int kr_rule_local_data_nxdomain(const knot_dname_t *, kr_rule_tags_t);
+int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
 int kr_rule_zonefile(const struct kr_rule_zonefile_config *);
 int kr_rule_forward(const knot_dname_t *, kr_rule_fwd_flags_t, const struct sockaddr **);
 int kr_rule_local_address(const char *, const char *, _Bool, uint32_t, kr_rule_tags_t);
index 993b092c1d8199721f140f1890d6998177782ced..11afea269c711550e1e340a09586c8daade4aeb2 100644 (file)
@@ -348,10 +348,11 @@ struct kr_query_data_src {
        kr_rule_fwd_flags_t flags;
        knot_db_val_t targets_ptr;
 };
-
+enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT};
 kr_layer_t kr_layer_t_static;
 _Bool kr_dbg_assertion_abort;
 int kr_dbg_assertion_fork;
+const uint32_t KR_RULE_TTL_DEFAULT;
 
 typedef int32_t (*kr_stale_cb)(int32_t ttl, const knot_dname_t *owner, uint16_t type,
                                const struct kr_query *qry);
@@ -495,8 +496,7 @@ int kr_rules_init(const char *, size_t);
 int kr_view_insert_action(const char *, const char *);
 int kr_view_select_action(const struct kr_request *, knot_db_val_t *);
 int kr_rule_tag_add(const char *, kr_rule_tags_t *);
-int kr_rule_local_data_emptyzone(const knot_dname_t *, kr_rule_tags_t);
-int kr_rule_local_data_nxdomain(const knot_dname_t *, kr_rule_tags_t);
+int kr_rule_local_subtree(const knot_dname_t *, enum kr_rule_sub_t, uint32_t, kr_rule_tags_t);
 int kr_rule_zonefile(const struct kr_rule_zonefile_config *);
 int kr_rule_forward(const knot_dname_t *, kr_rule_fwd_flags_t, const struct sockaddr **);
 int kr_rule_local_address(const char *, const char *, _Bool, uint32_t, kr_rule_tags_t);
index d4052cc0300153c9c9d5f93b0657d02bb7357a5a..609fe044e8f3942f73f9e4203ad9b38b1adf1847 100755 (executable)
@@ -147,12 +147,16 @@ ${CDEFS} ${LIBKRES} types <<-EOF
        kr_log_level_t
        enum kr_log_group
        struct kr_query_data_src
+       enum kr_rule_sub_t
 EOF
 
-${CDEFS} ${LIBKRES} variables <<-EOF
+${CDEFS} ${KRESD} variables <<-EOF
        kr_layer_t_static
+EOF
+${CDEFS} ${LIBKRES} variables <<-EOF
        kr_dbg_assertion_abort
        kr_dbg_assertion_fork
+       KR_RULE_TTL_DEFAULT
 EOF
 
 printf "
@@ -292,8 +296,7 @@ ${CDEFS} ${LIBKRES} functions <<-EOF
        kr_view_insert_action
        kr_view_select_action
        kr_rule_tag_add
-       kr_rule_local_data_emptyzone
-       kr_rule_local_data_nxdomain
+       kr_rule_local_subtree
        kr_rule_zonefile
        kr_rule_forward
        kr_rule_local_address
index 507935858dc56073fb2457de985e4a35321e72fc..8c047535bbe6d36c469cb041bbb68ae5ef2d5170 100644 (file)
@@ -104,12 +104,20 @@ It provides various input formats described in following subsections.
 
       .. option:: type: empty|nxdomain|redirect
 
-         Type of a subtree.
+         Type of this subtree:
+
+          - ``empty`` is an empty zone with just SOA and NS at the top
+          - ``nxdomain`` replies ``NXDOMAIN`` everywhere, though in some cases that looks slightly weird
+          - ``redirect`` answers with local-data records from the top of the zone, inside the whole virtual subtree
 
       .. option:: tags: <list of tags>
 
          Optional, tags to link with other policy rules, e.g. :ref:`views <config-views>`.
 
+      .. option:: ttl: <time s|m|h|d>
+
+         Optional, TTL of answers from this rule.  Uses ``/local-data/ttl`` if unspecified.
+
    .. future
       .. option:: addresses: <list of addresses>
 
index 99c0b36bb9d8938d7e91174b04d933427bd8df34..8ff809d2c46e121a8debe36232a4cfd6a5ef6492 100644 (file)
@@ -11,6 +11,8 @@
 
 struct kr_rules *the_rules = NULL;
 
+const uint32_t KR_RULE_TTL_DEFAULT = RULE_TTL_DEFAULT;
+
 /* DB key-space summary
 
  - "\0" starts special keys like "\0rulesets" or "\0stamp"
@@ -412,14 +414,14 @@ int rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt)
                        }
                        // Finally execute the rule.
                        switch (ztype) {
-                       case VAL_ZLAT_EMPTY:
-                       case VAL_ZLAT_NXDOMAIN:
-                       case VAL_ZLAT_NODATA:
+                       case KR_RULE_SUB_EMPTY:
+                       case KR_RULE_SUB_NXDOMAIN:
+                       case KR_RULE_SUB_NODATA:
                                ret = answer_zla_empty(ztype, qry, pkt, zla_lf, ttl);
                                if (ret == kr_error(EAGAIN))
                                        goto shorten;
                                return ret ? ret : RET_ANSWERED;
-                       case VAL_ZLAT_REDIRECT:
+                       case KR_RULE_SUB_REDIRECT:
                                ret = answer_zla_redirect(qry, pkt, ruleset_name, zla_lf, ttl);
                                return ret ? kr_error(ret) : RET_ANSWERED;
                        default:
@@ -622,8 +624,8 @@ fallback:
 static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt,
                                const knot_db_val_t zla_lf, uint32_t ttl)
 {
-       if (kr_fails_assert(type == VAL_ZLAT_EMPTY || type == VAL_ZLAT_NXDOMAIN
-                               || type == VAL_ZLAT_NODATA))
+       if (kr_fails_assert(type == KR_RULE_SUB_EMPTY || type == KR_RULE_SUB_NXDOMAIN
+                               || type == KR_RULE_SUB_NODATA))
                return kr_error(EINVAL);
 
        knot_dname_t apex_name[KNOT_DNAME_MAXLEN];
@@ -631,7 +633,7 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_
        CHECK_RET(ret);
 
        const bool hit_apex = knot_dname_is_equal(qry->sname, apex_name);
-       if (hit_apex && type == VAL_ZLAT_NODATA)
+       if (hit_apex && type == KR_RULE_SUB_NODATA)
                return kr_error(EAGAIN);
 
        /* Start constructing the (pseudo-)packet. */
@@ -641,7 +643,8 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_
        memset(&arrset, 0, sizeof(arrset));
 
        /* Construct SOA or NS data (hardcoded content).  _EMPTY has a proper zone apex. */
-       const bool want_NS = hit_apex && type == VAL_ZLAT_EMPTY && qry->stype == KNOT_RRTYPE_NS;
+       const bool want_NS = hit_apex && type == KR_RULE_SUB_EMPTY
+                               && qry->stype == KNOT_RRTYPE_NS;
        arrset.set.rr = knot_rrset_new(apex_name, want_NS ? KNOT_RRTYPE_NS : KNOT_RRTYPE_SOA,
                                        KNOT_CLASS_IN, ttl, &pkt->mm);
        if (kr_fails_assert(arrset.set.rr))
@@ -659,12 +662,12 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_
        arrset.set.expiring = false;
 
        /* Small differences if we exactly hit the name or even type. */
-       if (type == VAL_ZLAT_NODATA || (type == VAL_ZLAT_EMPTY && hit_apex)) {
+       if (type == KR_RULE_SUB_NODATA || (type == KR_RULE_SUB_EMPTY && hit_apex)) {
                knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NOERROR);
        } else {
                knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NXDOMAIN);
        }
-       if (type == VAL_ZLAT_EMPTY && hit_apex
+       if (type == KR_RULE_SUB_EMPTY && hit_apex
                        && (qry->stype == KNOT_RRTYPE_SOA || qry->stype == KNOT_RRTYPE_NS)) {
                ret = knot_pkt_begin(pkt, KNOT_ANSWER);
        } else {
@@ -682,7 +685,7 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_
        qry->flags.NO_MINIMIZE = true;
 
        VERBOSE_MSG(qry, "=> satisfied by local data (%s zone)\n",
-                    type == VAL_ZLAT_EMPTY ? "empty" : "nxdomain");
+                    type == KR_RULE_SUB_EMPTY ? "empty" : "nxdomain");
        return kr_ok();
 }
 
@@ -765,10 +768,24 @@ knot_db_val_t zla_key(const knot_dname_t *apex, uint8_t key_data[KEY_MAXLEN])
        key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data;
        return key;
 }
-int insert_trivial_zone(val_zla_type_t ztype, uint32_t ttl,
-                       const knot_dname_t *apex, kr_rule_tags_t tags)
+int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
+                         uint32_t ttl, kr_rule_tags_t tags)
 {
+       // type-check
+       switch (type) {
+       case KR_RULE_SUB_EMPTY:
+       case KR_RULE_SUB_NXDOMAIN:
+       case KR_RULE_SUB_NODATA:
+       case KR_RULE_SUB_REDIRECT:
+               break;
+       default:
+               kr_assert(false);
+               return kr_error(EINVAL);
+       }
+       const val_zla_type_t ztype = type;
+
        ENSURE_the_rules;
+
        uint8_t key_data[KEY_MAXLEN];
        knot_db_val_t key = zla_key(apex, key_data);
 
@@ -796,23 +813,6 @@ int insert_trivial_zone(val_zla_type_t ztype, uint32_t ttl,
        return kr_ok();
 }
 
-int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
-{
-       return insert_trivial_zone(VAL_ZLAT_EMPTY, RULE_TTL_DEFAULT, apex, tags);
-}
-int kr_rule_local_data_nxdomain(const knot_dname_t *apex, kr_rule_tags_t tags)
-{
-       return insert_trivial_zone(VAL_ZLAT_NXDOMAIN, RULE_TTL_DEFAULT, apex, tags);
-}
-int kr_rule_local_data_nodata(const knot_dname_t *apex, kr_rule_tags_t tags)
-{
-       return insert_trivial_zone(VAL_ZLAT_NODATA, RULE_TTL_DEFAULT, apex, tags);
-}
-int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags)
-{
-       return insert_trivial_zone(VAL_ZLAT_REDIRECT, RULE_TTL_DEFAULT, apex, tags);
-}
-
 
 /** Encode a subnet into a (longer) string.
  *
index d791d2966f3128f21028e27f58369750c552fc9a..44a4f3b89d542552b8c9897778652bfb6296c881 100644 (file)
@@ -56,6 +56,13 @@ KR_EXPORT
 int kr_view_select_action(const struct kr_request *req, knot_db_val_t *selected);
 
 
+/** Default TTL for answers from local data rules.
+ *
+ * Some types of rules save space when using this default.
+ * This definition exists mainly for usage from lua.
+ */
+KR_EXPORT extern
+const uint32_t KR_RULE_TTL_DEFAULT;
 
 /* APIs to modify the rule DB.
  *
@@ -116,28 +123,25 @@ int kr_rule_local_hosts(const char *path, bool use_nodata, uint32_t ttl, kr_rule
 KR_EXPORT
 int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags);
 
-// TODO: perhaps expose an enum to unify these simple subtree rules?
 
-/** Insert an empty zone.
+enum kr_rule_sub_t {
+       /// Empty zone, i.e. with SOA and NS
+       KR_RULE_SUB_EMPTY = 1,
+       /// NXDOMAIN for everything; TODO: SOA owner is hard.
+       KR_RULE_SUB_NXDOMAIN,
+       /// NODATA answers but not on exact name (e.g. it's similar to DNAME)
+       KR_RULE_SUB_NODATA,
+       /// Redirect: anything beneath has the same data as apex (except NS+SOA).
+       KR_RULE_SUB_REDIRECT,
+};
+/** Insert a simple sub-tree rule.
  *
  * - into the default rule-set
  * - SOA and NS for generated answers aren't overridable.
- * - TTL is RULE_TTL_DEFAULT
  */
 KR_EXPORT
-int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags);
-
-/** Insert an "NXDOMAIN zone".  TODO: SOA owner is hard. */
-KR_EXPORT
-int kr_rule_local_data_nxdomain(const knot_dname_t *apex, kr_rule_tags_t tags);
-/** Insert a "NODATA zone".  These functions are all similar. */
-KR_EXPORT
-int kr_rule_local_data_nodata(const knot_dname_t *apex, kr_rule_tags_t tags);
-
-/** Insert a redirect zone.
- * Into the default rule-set ATM.  SOA for generated NODATA answers isn't overridable. */
-KR_EXPORT
-int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags);
+int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
+                         uint32_t ttl, kr_rule_tags_t tags);
 
 /** Insert a view action into the default ruleset.
  *
index a518a98c7b056d389ae8544b4915df7f7e3141bc..bd50b5f61ccbf37eaf0bdedf5d565d486dcffb99 100644 (file)
@@ -136,18 +136,20 @@ int rules_defaults_insert(void)
                knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
                const knot_dname_t *dname =
                        knot_dname_from_str(name_buf, names[i], sizeof(name_buf));
-               int ret = kr_rule_local_data_emptyzone(dname, KR_RULE_TAGS_ALL);
+               int ret = kr_rule_local_subtree(dname, KR_RULE_SUB_EMPTY,
+                                               RULE_TTL_DEFAULT, KR_RULE_TAGS_ALL);
                CHECK_RET(ret);
                /* The double conversion is perhaps a bit wasteful, but it should be rare. */
                /* LATER: add extra info with explanation?  policy module had an ADDITIONAL
                 * record with explanation, but perhaps extended errors are more suitable?
-                * Differentiating the message - perhaps splitting VAL_ZLAT_EMPTY into a few?
+                * Differentiating the message - perhaps splitting KR_RULE_SUB_EMPTY into a few?
                 */
        }
 
        knot_dname_t localhost_dname[] = "\x09localhost\0";
        { // forward localhost
-               int ret = kr_rule_local_data_redirect(localhost_dname, KR_RULE_TAGS_ALL);
+               int ret = kr_rule_local_subtree(localhost_dname, KR_RULE_SUB_REDIRECT,
+                                               RULE_TTL_DEFAULT, KR_RULE_TAGS_ALL);
                CHECK_RET(ret);
 
                knot_rrset_t rr = {
index fe6c1e0355ff74fe4e129065c805e5277d3734bf..1ff78140a81d98128ce26d8bbf038dbe6f5d80c0 100644 (file)
@@ -46,28 +46,11 @@ int rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt);
 
 /** The first byte of zone-like apex value is its type. */
 typedef uint8_t val_zla_type_t;
+/** This effectively contains enum kr_rule_sub_t */
 enum {
-       /** Empty zone. No data in DB value after this byte.
-        *
-        * TODO: add
-        *  - SOA rdata (maybe, optional, remainder of DB value)
-        *  Same for _NXDOMAIN and _NODATA, too.
-        */
-       VAL_ZLAT_EMPTY = 1,
-       /** Forced NXDOMAIN. */
-       VAL_ZLAT_NXDOMAIN,
-       /** Forced NODATA.  Does not apply on exact name (e.g. it's similar to DNAME) */
-       VAL_ZLAT_NODATA,
-       /** Redirect: anything beneath has the same data as apex (except NS+SOA). */
-       VAL_ZLAT_REDIRECT,
        /** Forward, i.e. override upstream for this subtree (resolver or auth). */
-       VAL_ZLAT_FORWARD,
+       VAL_ZLAT_FORWARD = 128,
 };
-/** For now see kr_rule_local_data_emptyzone() and friends.
- *
- * TODO: probably make something like this the preferred API. */
-int insert_trivial_zone(val_zla_type_t ztype, uint32_t ttl,
-                       const knot_dname_t *apex, kr_rule_tags_t tags);
 
 extern /*const*/ char RULESET_DEFAULT[];
 
index fc5ff1f51035f20479b99c352999cda4b3ae3e77..da53675f1684782e9da7707a650e940bbaa3687d 100644 (file)
@@ -84,13 +84,14 @@ static void cname_scan2rule(zs_scanner_t *s)
                // Exact RPZ semantics would be hard here, it makes more sense
                // to apply also to a subtree, and corresponding wildcard rule
                // usually accompanies this rule anyway.
-               ret = insert_trivial_zone(VAL_ZLAT_NXDOMAIN, s->r_ttl, apex, c->tags);
+               ret = kr_rule_local_subtree(apex, KR_RULE_SUB_NXDOMAIN, s->r_ttl, c->tags);
        } else if (knot_dname_is_wildcard(s->r_data) && s->r_data[2] == 0) {
                // "CNAME *." -> NODATA
                knot_dname_t *apex = s->r_owner;
                if (knot_dname_is_wildcard(apex)) {
                        apex += 2;
-                       ret = insert_trivial_zone(VAL_ZLAT_NODATA, s->r_ttl, apex, c->tags);
+                       ret = kr_rule_local_subtree(apex, KR_RULE_SUB_NODATA,
+                                                       s->r_ttl, c->tags);
                } else { // using special kr_rule_ semantics of empty CNAME RRset
                        knot_rrset_t rrs;
                        knot_rrset_init(&rrs, apex, KNOT_RRTYPE_CNAME,
index 614ae7efd108b74250fc242443b60ba9dbe9b567..ed9f1d81672aaa83e839f7240f78e8210ccd3ba8 100644 (file)
@@ -41,7 +41,7 @@ hints.ttl({{ cfg.local_data.ttl.seconds() }})
 {% for subtree in cfg.local_data.subtrees %}
 {% if subtree.roots -%}
 {% for root in subtree.roots %}
-{{ local_data_subtree_root(subtree.type, root, subtree.tags) }}
+{{ local_data_subtree_root(root, subtree.type, subtree.ttl or cfg.local_data.ttl, subtree.tags) }}
 {% endfor %}
 {%- elif subtree.roots_file -%}
 {# TODO: not implemented yet #}
index 283c0f248faaaa42cacb9810ba4ec4c8306fdad5..5fce85fa585f8caeb47503a3c58d4ba976609445 100644 (file)
@@ -54,25 +54,17 @@ assert(hints.add_hosts('{{ file }}').result == true)
 assert(C.kr_rule_zonefile({{ id }})==0)
 {%- endmacro %}
 
-{% macro local_data_emptyzone(dname, tags) -%}
-assert(C.kr_rule_local_data_emptyzone({{ dname }},{{ tags }})==0)
-{%- endmacro %}
-
-{% macro local_data_nxdomain(dname, tags) -%}
-assert(C.kr_rule_local_data_nxdomain({{ dname }},{{ tags }})==0)
-{%- endmacro %}
-
-{% macro local_data_subtree_root(type, root, tags) -%}
+{% macro local_data_subtree_root(root, type, ttl, tags) -%}
+{% if ttl %}
+{%- set get_ttl = ttl.seconds() -%}
+{%- else -%}
+{%- set get_ttl = 'C.KR_RULE_TTL_DEFAULT' -%}
+{% endif %}
 {%- if tags -%}
 {%- set get_tags = policy_get_tagset(tags) -%}
 {%- else -%}
 {%- set get_tags = '0' -%}
 {%- endif -%}
-{%- if type == 'empty' -%}
-{{ local_data_emptyzone(policy_todname(root), get_tags) }}
-{%- elif type == 'nxdomain' -%}
-{{ local_data_nxdomain(policy_todname(root), get_tags) }}
-{%- else -%}
-{# TODO: implement other possible types #}
-{%- endif -%}
+assert(C.kr_rule_local_subtree(todname('{{ root }}'),
+       C.KR_RULE_SUB_{{ type.upper() }}, {{ get_ttl }}, {{ get_tags }}) == 0)
 {%- endmacro %}