From: Vladimír Čunát Date: Thu, 30 Jun 2022 12:25:21 +0000 (+0200) Subject: lib/rules: implement redirect zones X-Git-Tag: v6.0.1~9^2~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=33bceafce1460a1827cbed89df1efa7fbfda0559;p=thirdparty%2Fknot-resolver.git lib/rules: implement redirect zones Also switch the localhost rule there, finishing migration of all special names from the policy module. --- diff --git a/lib/rules/api.c b/lib/rules/api.c index fb15bf1a0..d9cd46ff7 100644 --- a/lib/rules/api.c +++ b/lib/rules/api.c @@ -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); +} + diff --git a/lib/rules/api.h b/lib/rules/api.h index d5b6b7993..dd3f2bf68 100644 --- a/lib/rules/api.h +++ b/lib/rules/api.h @@ -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); + diff --git a/lib/rules/defaults.c b/lib/rules/defaults.c index 8c96b5619..a518a98c7 100644 --- a/lib/rules/defaults.c +++ b/lib/rules/defaults.c @@ -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(); diff --git a/modules/policy/policy.lua b/modules/policy/policy.lua index 5b2910e29..64689801d 100644 --- a/modules/policy/policy.lua +++ b/modules/policy/policy.lua @@ -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)