]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/rules: implement redirect zones
authorVladimír Čunát <vladimir.cunat@nic.cz>
Thu, 30 Jun 2022 12:25:21 +0000 (14:25 +0200)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Mon, 12 Jun 2023 08:32:28 +0000 (10:32 +0200)
Also switch the localhost rule there, finishing migration
of all special names from the policy module.

lib/rules/api.c
lib/rules/api.h
lib/rules/defaults.c
modules/policy/policy.lua

index fb15bf1a077d0c47a18395b0bbc78870edef7126..d9cd46ff7205b55e537df39717fd01222952157e 100644 (file)
@@ -47,6 +47,8 @@ typedef uint8_t val_zla_type_t;
 enum {
        /** Empty zone. No data in DB value after this byte. */
        VAL_ZLAT_EMPTY = 1,
+       /** Redirect: anything beneath has the same data as apex (except NS+SOA). */
+       VAL_ZLAT_REDIRECT,
 };
 
 
@@ -54,6 +56,8 @@ static int answer_exact_match(struct kr_query *qry, knot_pkt_t *pkt, uint16_t ty
                const uint8_t *data, const uint8_t *data_bound);
 static int answer_zla_empty(struct kr_query *qry, knot_pkt_t *pkt,
                knot_db_val_t zla_lf, 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, knot_db_val_t val);
 
 //TODO later, maybe.  ATM it would be cumbersome to avoid void* arithmetics.
 #pragma GCC diagnostic ignored "-Wpointer-arith"
@@ -140,7 +144,7 @@ static bool kr_rule_consume_tags(knot_db_val_t *val, const struct kr_request *re
  *
  * Note: key_data[KEY_DNAME_END_OFFSET] = '\0' even though
  * not always used as a part of the key. */
-static inline uint8_t * key_dname_lf(const knot_dname_t *name, uint8_t *key_data)
+static inline uint8_t * key_dname_lf(const knot_dname_t *name, uint8_t key_data[KEY_MAXLEN])
 {
        return knot_dname_lf(name, key_data + KEY_RULESET_MAXLEN + 1)
                + 1/*drop length*/;
@@ -211,6 +215,7 @@ int kr_rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt)
 
        /* Iterate over all rulesets. */
        while (rulesets.len > 0) {
+               const char * const ruleset_name = rulesets_str;
                { /* Write ruleset-specific prefix of the key. */
                        const size_t rsp_len = strnlen(rulesets_str, rulesets.len);
                        kr_require(rsp_len <= KEY_RULESET_MAXLEN - 1);
@@ -298,6 +303,8 @@ int kr_rule_local_data_answer(struct kr_query *qry, knot_pkt_t *pkt)
                        switch (ztype) {
                        case VAL_ZLAT_EMPTY:
                                return answer_zla_empty(qry, pkt, zla_lf, val);
+                       case VAL_ZLAT_REDIRECT:
+                               return answer_zla_redirect(qry, pkt, ruleset_name, zla_lf, val);
                        default:
                                return kr_error(EILSEQ);
                        }
@@ -387,7 +394,8 @@ static int answer_exact_match(struct kr_query *qry, knot_pkt_t *pkt, uint16_t ty
 }
 
 
-static knot_db_val_t local_data_key(const knot_rrset_t *rrs, uint8_t key_data[KEY_MAXLEN])
+static knot_db_val_t local_data_key(const knot_rrset_t *rrs, uint8_t key_data[KEY_MAXLEN],
+                                       const char *ruleset_name)
 {
        knot_db_val_t key;
        key.data = key_dname_lf(rrs->owner, key_data);
@@ -396,9 +404,9 @@ static knot_db_val_t local_data_key(const knot_rrset_t *rrs, uint8_t key_data[KE
        key.data -= sizeof(KEY_EXACT_MATCH);
        memcpy(key.data, &KEY_EXACT_MATCH, sizeof(KEY_EXACT_MATCH));
 
-       const size_t rsp_len = strlen(RULESET_DEFAULT);
+       const size_t rsp_len = strlen(ruleset_name);
        key.data -= rsp_len;
-       memcpy(key.data, RULESET_DEFAULT, rsp_len);
+       memcpy(key.data, ruleset_name, rsp_len);
 
        memcpy(key_data + KEY_DNAME_END_OFFSET + 2, &rrs->type, sizeof(rrs->type));
        key.len = key_data + KEY_DNAME_END_OFFSET + 2 + sizeof(rrs->type)
@@ -410,7 +418,7 @@ int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_r
 {
        // Construct the DB key.
        uint8_t key_data[KEY_MAXLEN];
-       knot_db_val_t key = local_data_key(rrs, key_data);
+       knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
 
        // Allocate the data in DB.
        const int rr_ssize = rdataset_dematerialize_size(&rrs->rrs);
@@ -434,7 +442,7 @@ int kr_rule_local_data_ins(const knot_rrset_t *rrs, const knot_rdataset_t *sig_r
 int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags)
 {
        uint8_t key_data[KEY_MAXLEN];
-       knot_db_val_t key = local_data_key(rrs, key_data);
+       knot_db_val_t key = local_data_key(rrs, key_data, RULESET_DEFAULT);
        int ret = ruledb_op(remove, &key, 1);
        if (ret != 1)
                return ret;
@@ -504,7 +512,76 @@ static int answer_zla_empty(struct kr_query *qry, knot_pkt_t *pkt,
        return kr_ok();
 }
 
-int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
+static int answer_zla_redirect(struct kr_query *qry, knot_pkt_t *pkt, const char *ruleset_name,
+                               const knot_db_val_t zla_lf, const knot_db_val_t val_unused)
+{
+       if (kr_fails_assert(val_unused.len == 0)) {
+               kr_log_error(RULES, "ERROR: unused bytes: %zu\n", val_unused.len);
+               return kr_error(EILSEQ);
+       }
+       VERBOSE_MSG(qry, "=> redirecting by local data\n"); // lazy to get the zone name
+
+       knot_dname_t apex_name[KNOT_DNAME_MAXLEN];
+       int ret = knot_dname_lf2wire(apex_name, zla_lf.len, zla_lf.data);
+       CHECK_RET(ret);
+       const bool name_matches = knot_dname_is_equal(qry->sname, apex_name);
+       if (name_matches || qry->stype == KNOT_RRTYPE_NS || qry->stype == KNOT_RRTYPE_SOA)
+               goto nodata;
+
+       // Reconstruct the DB key from scratch.
+       knot_rrset_t rrs;
+       knot_rrset_init(&rrs, apex_name, qry->stype, 0, 0); // 0 are unused
+       uint8_t key_data[KEY_MAXLEN];
+       knot_db_val_t key = local_data_key(&rrs, key_data, ruleset_name);
+
+       knot_db_val_t val;
+       ret = ruledb_op(read, &key, &val, 1);
+       switch (ret) {
+               case -ENOENT: goto nodata;
+               case 0: break;
+               default: return ret;
+       }
+       if (kr_rule_consume_tags(&val, qry->request)) // found a match
+               return answer_exact_match(qry, pkt, qry->stype,
+                                               val.data, val.data + val.len);
+
+nodata: // Want NODATA answer (or NOERROR if it hits apex SOA).
+       // Start constructing the (pseudo-)packet.
+       ret = pkt_renew(pkt, qry->sname, qry->stype);
+       CHECK_RET(ret);
+       struct answer_rrset arrset;
+       memset(&arrset, 0, sizeof(arrset));
+       arrset.set.rr = knot_rrset_new(apex_name, KNOT_RRTYPE_SOA,
+                                       KNOT_CLASS_IN, RULE_TTL_DEFAULT, &pkt->mm);
+       if (kr_fails_assert(arrset.set.rr))
+               return kr_error(ENOMEM);
+       ret = knot_rrset_add_rdata(arrset.set.rr, soa_rdata,
+                                       sizeof(soa_rdata) - 1, &pkt->mm);
+       CHECK_RET(ret);
+       arrset.set.rank = KR_RANK_SECURE | KR_RANK_AUTH; // local data has high trust
+       arrset.set.expiring = false;
+
+       knot_wire_set_rcode(pkt->wire, KNOT_RCODE_NOERROR);
+       knot_section_t sec = name_matches && qry->stype == KNOT_RRTYPE_SOA
+                               ? KNOT_ANSWER : KNOT_AUTHORITY;
+       ret = knot_pkt_begin(pkt, sec);
+       CHECK_RET(ret);
+
+       // Put links to the RR into the pkt.
+       ret = pkt_append(pkt, &arrset);
+       CHECK_RET(ret);
+
+       // Finishing touches.
+       qry->flags.EXPIRING = false;
+       qry->flags.CACHED = true;
+       qry->flags.NO_MINIMIZE = true;
+
+       VERBOSE_MSG(qry, "=> satisfied by local data (no data)\n");
+       return kr_ok();
+}
+
+static int insert_trivial_zone(val_zla_type_t ztype,
+                              const knot_dname_t *apex, kr_rule_tags_t tags)
 {
        uint8_t key_data[KEY_MAXLEN];
        knot_db_val_t key;
@@ -518,7 +595,6 @@ int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
        memcpy(key.data, RULESET_DEFAULT, rsp_len);
        key.len = key_data + KEY_DNAME_END_OFFSET - (uint8_t *)key.data;
 
-       val_zla_type_t ztype = VAL_ZLAT_EMPTY;
        knot_db_val_t val = {
                .data = NULL,
                .len = sizeof(tags) + sizeof(ztype),
@@ -533,3 +609,12 @@ int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
        return ruledb_op(commit);
 }
 
+int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags)
+{
+       return insert_trivial_zone(VAL_ZLAT_EMPTY, 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, apex, tags);
+}
+
index d5b6b7993f3815b7c3b80e094a4231569fb04130..dd3f2bf6821079e6963aabcba4a0ca030b3cc9fb 100644 (file)
@@ -51,7 +51,12 @@ KR_EXPORT
 int kr_rule_local_data_del(const knot_rrset_t *rrs, kr_rule_tags_t tags);
 
 /** Insert an empty zone.
- * Into the default rule-set ATM. */
+ * Into the default rule-set ATM.  SOA for generated NODATA isn't overridable. */
 KR_EXPORT
 int kr_rule_local_data_emptyzone(const knot_dname_t *apex, kr_rule_tags_t tags);
 
+/** Insert a redirect zone.
+ * Into the default rule-set ATM.  SOA for generated NODATA isn't overridable. */
+KR_EXPORT
+int kr_rule_local_data_redirect(const knot_dname_t *apex, kr_rule_tags_t tags);
+
index 8c96b561911ea4aa15cdec0911d149894fa5ddd9..a518a98c7b056d389ae8544b4915df7f7e3141bc 100644 (file)
@@ -6,6 +6,10 @@
 #include "lib/rules/api.h"
 #include "lib/utils.h"
 
+#define CHECK_RET(ret) do { \
+       if ((ret) < 0) { kr_assert(false); return kr_error((ret)); } \
+} while (false)
+
 int rules_defaults_insert(void)
 {
        static const char * names[] = {
@@ -133,8 +137,7 @@ int rules_defaults_insert(void)
                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);
-               if (kr_fails_assert(!ret))
-                       return kr_error(ret);
+               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?
@@ -142,30 +145,64 @@ int rules_defaults_insert(void)
                 */
        }
 
+       knot_dname_t localhost_dname[] = "\x09localhost\0";
+       { // forward localhost
+               int ret = kr_rule_local_data_redirect(localhost_dname, KR_RULE_TAGS_ALL);
+               CHECK_RET(ret);
+
+               knot_rrset_t rr = {
+                       .owner = localhost_dname,
+                       .ttl = RULE_TTL_DEFAULT,
+                       .rclass = KNOT_CLASS_IN,
+                       .rrs = { 0 },
+                       .additional = NULL,
+               };
+               rr.type = KNOT_RRTYPE_A;
+               ret = knot_rrset_add_rdata(&rr, (const uint8_t *)"\x7f\0\0\1", 4, NULL);
+               if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+               knot_rdataset_clear(&rr.rrs, NULL);
+               CHECK_RET(ret);
+
+               rr.type = KNOT_RRTYPE_AAAA;
+               ret = knot_rrset_add_rdata(&rr,
+                               (const uint8_t *)"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1",
+                               16, NULL);
+               if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+               knot_rdataset_clear(&rr.rrs, NULL);
+               CHECK_RET(ret);
+
+               rr.type = KNOT_RRTYPE_NS;
+               ret = knot_rrset_add_rdata(&rr, localhost_dname, 1+9+1, NULL);
+               if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+               knot_rdataset_clear(&rr.rrs, NULL);
+               CHECK_RET(ret);
+       }
+
        { // reverse localhost; LATER: the situation isn't ideal with NXDOMAIN + some exact matches
-               knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
                knot_rrset_t rr = {
-                       .owner = knot_dname_from_str(name_buf,
-                               "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
-                               sizeof(name_buf)),
+                       .owner = localhost_dname,
                        .ttl = RULE_TTL_DEFAULT,
                        .type = KNOT_RRTYPE_PTR,
                        .rclass = KNOT_CLASS_IN,
                        .rrs = { 0 },
                        .additional = NULL,
                };
-               int ret = knot_rrset_add_rdata(&rr, (const knot_dname_t *)"\x09localhost\0",
-                                               1+9+1, NULL);
+               int ret = knot_rrset_add_rdata(&rr, localhost_dname, 1+9+1, NULL);
                if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
 
+               knot_dname_t name_buf[KNOT_DNAME_MAXLEN];
                rr.owner = knot_dname_from_str(name_buf,
                                "1.0.0.127.in-addr.arpa.",
                                sizeof(name_buf));
                if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
 
+               rr.owner = knot_dname_from_str(name_buf,
+                       "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.",
+                       sizeof(name_buf));
+               if (!ret) ret = kr_rule_local_data_ins(&rr, NULL, KR_RULE_TAGS_ALL);
+
                knot_rdataset_clear(&rr.rrs, NULL);
-               if (kr_fails_assert(!ret))
-                       return kr_error(ret);
+               CHECK_RET(ret);
        }
 
        return kr_ok();
index 5b2910e299763f6aab339b5aa370f50bb33eabf4..64689801d1f845c5a78660ea8407716b714e81a3 100644 (file)
@@ -5,8 +5,6 @@ local ffi = require('ffi')
 local LOG_GRP_POLICY_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_POLICY))
 local LOG_GRP_REQDBG_TAG = ffi.string(ffi.C.kr_log_grp2name(ffi.C.LOG_GRP_REQDBG))
 
-local todname = kres.str2dname -- not available during module load otherwise
-
 -- Counter of unique rules
 local nextid = 0
 local function getruleid()
@@ -270,35 +268,6 @@ function policy.ANSWER(rtable, nodata)
        end
 end
 
-local dname_localhost = todname('localhost.')
-
--- Rule for localhost. zone; see RFC6303, sec. 3
-local function localhost(_, req)
-       local qry = req:current()
-       local answer = req:ensure_answer()
-       if answer == nil then return nil end
-       ffi.C.kr_pkt_make_auth_header(answer)
-
-       local is_exact = ffi.C.knot_dname_is_equal(qry.sname, dname_localhost)
-
-       answer:rcode(kres.rcode.NOERROR)
-       answer:begin(kres.section.ANSWER)
-       if qry.stype == kres.type.AAAA then
-               answer:put(qry.sname, 900, answer:qclass(), kres.type.AAAA,
-                       '\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1')
-       elseif qry.stype == kres.type.A then
-               answer:put(qry.sname, 900, answer:qclass(), kres.type.A, '\127\0\0\1')
-       elseif is_exact and qry.stype == kres.type.SOA then
-               mkauth_soa(answer, dname_localhost)
-       elseif is_exact and qry.stype == kres.type.NS then
-               answer:put(dname_localhost, 900, answer:qclass(), kres.type.NS, dname_localhost)
-       else
-               answer:begin(kres.section.AUTHORITY)
-               mkauth_soa(answer, dname_localhost)
-       end
-       return kres.DONE
-end
-
 -- All requests
 function policy.all(action)
        return function(_, _) return action end
@@ -864,29 +833,6 @@ end
 -- @var Default rules
 policy.rules = {}
 policy.postrules = {}
-policy.special_names = {
-       -- XXX: beware of special_names_optim() when modifying these filters
-       {
-               cb=policy.suffix(localhost, {dname_localhost}),
-               count=0
-       },
-}
-
--- Return boolean; false = no special name may apply, true = some might apply.
--- The point is to *efficiently* filter almost all QNAMEs that do not apply.
-local function special_names_optim(req, sname)
-       local qname_size = req.qsource.packet.qname_size
-       if qname_size < 9 then return true end -- don't want to special-case bad array access
-       local root = sname + qname_size - 1
-       return
-               -- .a???. or .t???.
-               (root[-5] == 4 and (root[-4] == 97 or root[-4] == 116))
-               -- .on???. or .in?????. or lo???. or *ost.
-               or (root[-6] == 5 and root[-5] == 111 and root[-4] == 110)
-               or (root[-8] == 7 and root[-7] == 105 and root[-6] == 110)
-               or (root[-6] == 5 and root[-5] == 108 and root[-4] == 111)
-               or (root[-3] == 111 and root[-2] == 115 and root[-1] == 116)
-end
 
 -- Top-down policy list walk until we hit a match
 -- the caller is responsible for reordering policy list
@@ -899,8 +845,6 @@ policy.layer = {
                if bit.band(state, bit.bor(kres.FAIL, kres.DONE)) ~= 0 then return state end
                local qry = req:initial() -- same as :current() but more descriptive
                return policy.evaluate(policy.rules, req, qry, state)
-                       or (special_names_optim(req, qry.sname)
-                                       and policy.evaluate(policy.special_names, req, qry, state))
                        or state
        end,
        finish = function(state, req)