]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/rules: support another RPZ feature docs-rpz-wild-0mwqkp/deployments/8580
authorVladimír Čunát <vladimir.cunat@nic.cz>
Mon, 9 Feb 2026 07:36:22 +0000 (08:36 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Fri, 13 Feb 2026 07:53:50 +0000 (08:53 +0100)
Some people want to do blocking by "redirection" to a page, e.g. via
  *.some.name.  CNAME  block.page.

NEWS
daemon/lua/kres-gen-33.lua
doc/user/config-local-data.rst
lib/rules/api.c
lib/rules/api.h
lib/rules/impl.h
lib/rules/zonefile.c

diff --git a/NEWS b/NEWS
index 34c76ccd1f4b1f6396fbdb4d7b1d8f7fec8bd06d..fefd40a573b687ed314f74d3bfa0f5f414e07a74 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,11 @@
+Knot Resolver 6.x.y (2026-0m-dd)
+================================
+
+Improvements:
+-------------
+- RPZ now supports  *.some.name. CNAME block.page. (!1808)
+
+
 Knot Resolver 6.2.0 (2026-02-03)
 ================================
 
index bba6888ab6d4905491e76cdc7ab892c553893360..1e4836520dc4b7896082b8cf2700b4b1ba98dbd4 100644 (file)
@@ -385,7 +385,7 @@ 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_RULE_SUB_DNAME};
+enum kr_rule_sub_t {KR_RULE_SUB_EMPTY = 1, KR_RULE_SUB_NXDOMAIN, KR_RULE_SUB_NODATA, KR_RULE_SUB_REDIRECT, KR_RULE_SUB_DNAME, KR_RULE_SUB_DNAME_FLAT};
 enum kr_proto {KR_PROTO_INTERNAL, KR_PROTO_UDP53, KR_PROTO_TCP53, KR_PROTO_DOT, KR_PROTO_DOH, KR_PROTO_DOQ, KR_PROTO_DOQ_CONN, KR_PROTO_DOQ_STREAM, KR_PROTO_COUNT};
 typedef unsigned char kr_proto_set;
 kr_layer_t kr_layer_t_static;
index 348983a7b981acc36ff91a84b8f439b49d5ae2b2..2f2f650438f2d10e661cbeca69f1941eaeac1f3b 100644 (file)
@@ -145,8 +145,8 @@ It provides various input formats described in following subsections.
    * just files which are *not* automatically reloaded when changed
    * rules with ``rpz-*`` labels are ignored, e.g. ``.rpz-client-ip``
    .. * ``CNAME *.some.thing`` does not expand the wildcard
-   * wildcard support is limited only to blocking subtrees by
-     ``*.some.name. CNAME .`` or ``*.some.name. CNAME *.``
+   * wildcard support is limited to left-hand side of CNAME:
+     ``*.some.name. CNAME .`` or ``*.some.name. CNAME *.`` or ``*.some.name. CNAME block.page.``
 
    Advanced rules
    --------------
index 2953578fe0e1e6f35c1b0453c865bd2a58633180..dcabb6d97b62304d15f84c25fa1ca6f3d580c410 100644 (file)
@@ -61,9 +61,6 @@ static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_
                                knot_db_val_t zla_lf, uint32_t ttl, knot_db_val_t *val);
 static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name,
                                knot_db_val_t zla_lf, uint32_t ttl);
-static int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
-                               const knot_dname_t *target, uint32_t ttl,
-                               kr_rule_tags_t tags, kr_rule_opts_t opts);
 
 // LATER: doing tag_names_default() and kr_rule_tag_add() inside a RW transaction would be better.
 static int tag_names_default(void)
@@ -493,6 +490,7 @@ int rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt)
                                ret = answer_zla_redirect(qry, pkt, ruleset_name, zla_lf, ttl);
                                break;
                        case KR_RULE_SUB_DNAME:
+                       case KR_RULE_SUB_DNAME_FLAT:
                                ret = answer_zla_dname(ztype, qry, pkt, zla_lf, ttl, &val);
                                break;
                        default:
@@ -782,7 +780,7 @@ static int answer_zla_empty(val_zla_type_t type, struct kr_query *qry, knot_pkt_
 static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_t *pkt,
                                const knot_db_val_t zla_lf, uint32_t ttl, knot_db_val_t *val)
 {
-       if (kr_fails_assert(type == KR_RULE_SUB_DNAME))
+       if (kr_fails_assert(type == KR_RULE_SUB_DNAME || type == KR_RULE_SUB_DNAME_FLAT))
                return kr_error(EINVAL);
        
        const knot_dname_t *dname_target = val->data;
@@ -804,7 +802,7 @@ static int answer_zla_dname(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 == KR_RULE_SUB_DNAME)
+       if (hit_apex)
                return kr_error(EAGAIN); // LATER: maybe a type that matches apex
 
        // Start constructing the (pseudo-)packet.
@@ -817,8 +815,9 @@ static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_
                                        KNOT_CLASS_IN, ttl, &pkt->mm);
        if (kr_fails_assert(arrset.set.rr))
                return kr_error(ENOMEM);
-       const knot_dname_t *cname_target = knot_dname_replace_suffix(qry->sname,
-                       knot_dname_labels(apex_name, NULL), dname_target, &pkt->mm);
+       const knot_dname_t *cname_target = type == KR_RULE_SUB_DNAME_FLAT ? dname_target :
+               knot_dname_replace_suffix(qry->sname, knot_dname_labels(apex_name, NULL),
+                                               dname_target, &pkt->mm);
        const int rdata_len = knot_dname_size(cname_target);
        const bool cname_fits = rdata_len <= KNOT_DNAME_MAXLEN;
        if (cname_fits) {
@@ -847,7 +846,7 @@ static int answer_zla_dname(val_zla_type_t type, struct kr_query *qry, knot_pkt_
        qry->flags.CACHED = true;
        qry->flags.NO_MINIMIZE = true;
 
-       VERBOSE_MSG(qry, "=> satisfied by local data (DNAME)\n");
+       VERBOSE_MSG(qry, "=> satisfied by local data (DNAME-like)\n");
        return kr_ok();
 }
 
@@ -936,14 +935,15 @@ 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;
 }
-static int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
-                               const knot_dname_t *target, uint32_t ttl,
-                               kr_rule_tags_t tags, kr_rule_opts_t opts)
+int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
+                       const knot_dname_t *target, uint32_t ttl,
+                       kr_rule_tags_t tags, kr_rule_opts_t opts)
 {
        // type-check
-       const bool has_target = (type == KR_RULE_SUB_DNAME);
+       const bool has_target = (type == KR_RULE_SUB_DNAME || type == KR_RULE_SUB_DNAME_FLAT);
        switch (type) {
        case KR_RULE_SUB_DNAME:
+       case KR_RULE_SUB_DNAME_FLAT:
                if (kr_fails_assert(!!target == has_target))
                        return kr_error(EINVAL);
                break;
index 258b017f11010e5b1985b825133910c4fd711380..feea98e09e5388c0b85d1d996dbd1e5eab6c6f6b 100644 (file)
@@ -205,12 +205,15 @@ enum kr_rule_sub_t {
        KR_RULE_SUB_REDIRECT,
        /// Act similar to DNAME: rebase everything underneath by generated CNAMEs.
        KR_RULE_SUB_DNAME,
+       /// Like _SUB_DNAME but the CNAMEs do not get prefixed.
+       KR_RULE_SUB_DNAME_FLAT,
 };
 /** Insert a simple sub-tree rule.
  *
  * - into the default rule-set
  * - SOA and NS for generated answers aren't overridable.
- * - type: you can't use _DNAME via this function; insert it by kr_rule_local_data_ins()
+ * - type: you can't use _DNAME* via this function;
+ *   internally we have rule_local_subtree() for now; TODO
  */
 KR_EXPORT
 int kr_rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
index bf82b581a947f2d60da985478e4658705eb878b0..f5d0c912b4544798f40a8355132005e8bf698f98 100644 (file)
@@ -39,6 +39,11 @@ int local_data_ins(knot_db_val_t key, const knot_rrset_t *rrs, const knot_rdatas
 /** Construct key for a zone-like-apex entry.  It's stored in `key_data`. */
 knot_db_val_t zla_key(const knot_dname_t *apex, uint8_t key_data[KEY_MAXLEN]);
 
+/** Like kr_rule_local_subtree() but has `target` to support other kr_rule_sub_t types. */
+int rule_local_subtree(const knot_dname_t *apex, enum kr_rule_sub_t type,
+                       const knot_dname_t *target, uint32_t ttl,
+                       kr_rule_tags_t tags, kr_rule_opts_t opts);
+
 /** Almost the whole kr_rule_local_data_answer() */
 int rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt);
 
index 44df2a819f3363d34e9853f251099f5e09d030dd..d6ade6d64579086375932e844201d8ccb3650ee4 100644 (file)
@@ -79,34 +79,37 @@ static void cname_scan2rule(zs_scanner_t *s)
                return;
        }
        int ret = 0;
-       if (s->r_data[0] == 0) { // "CNAME ." i.e. NXDOMAIN
-               const knot_dname_t *apex = s->r_owner;
-               if (knot_dname_is_wildcard(apex))
-                       apex += 2;
+       knot_dname_t *owner = s->r_owner;
+       const knot_dname_t *target = s->r_data;
+       if (target[0] == 0) { // "CNAME ." i.e. NXDOMAIN
+               if (knot_dname_is_wildcard(owner))
+                       owner += 2;
                // RPZ_COMPAT: we NXDOMAIN the whole subtree regardless of being wildcard.
                // 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 = kr_rule_local_subtree(apex, KR_RULE_SUB_NXDOMAIN,
+               ret = kr_rule_local_subtree(owner, KR_RULE_SUB_NXDOMAIN,
                                                s->r_ttl, c->tags, c->opts);
-       } else if (knot_dname_is_wildcard(s->r_data) && s->r_data[2] == 0) {
+       } else if (knot_dname_is_wildcard(target) && target[2] == 0) {
                // "CNAME *." -> NODATA
-               knot_dname_t *apex = s->r_owner;
-               if (knot_dname_is_wildcard(apex)) {
-                       apex += 2;
-                       ret = kr_rule_local_subtree(apex, KR_RULE_SUB_NODATA,
+               if (knot_dname_is_wildcard(owner)) {
+                       owner += 2;
+                       ret = kr_rule_local_subtree(owner, KR_RULE_SUB_NODATA,
                                                        s->r_ttl, c->tags, c->opts);
                } else { // using special kr_rule_ semantics of empty CNAME RRset
                        knot_rrset_t rrs;
-                       knot_rrset_init(&rrs, apex, KNOT_RRTYPE_CNAME,
+                       knot_rrset_init(&rrs, owner, KNOT_RRTYPE_CNAME,
                                        KNOT_CLASS_IN, s->r_ttl);
                        ret = kr_rule_local_data_ins(&rrs, NULL, c->tags, c->opts);
                }
+       } else if (knot_dname_is_wildcard(owner)) {
+               owner += 2;
+               ret = rule_local_subtree(owner, KR_RULE_SUB_DNAME_FLAT, target,
+                                               s->r_ttl, c->tags, c->opts);
+               // TODO: implement wildcard expansion for target
        } else {
-               knot_dname_t *target = s->r_owner;
                knot_rrset_t rrs;
-               knot_rrset_init(&rrs, target, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, s->r_ttl);
-               // TODO: implement wildcard expansion for target
+               knot_rrset_init(&rrs, s->r_owner, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN, s->r_ttl);
                ret = knot_rrset_add_rdata(&rrs, s->r_data, s->r_data_length, NULL);
                if (!ret) ret = kr_rule_local_data_ins(&rrs, NULL, c->tags, c->opts);
                knot_rdataset_clear(&rrs.rrs, NULL);