- cache: missing filesystem support for pre-allocation is no longer fatal (#549)
- lua: policy.rpz() no longer watches the file when watch is set to false (!954)
- fix a strict aliasing problem that might've lead to "miscompilation" (!962)
+- fix handling of DNAMEs, especially signed ones (#234, !965)
- lua resolve(): correctly include EDNS0 in the virtual packet (!963)
Custom modules might have been confused by that.
- do not leak bogus data into SERVFAIL answers (#396)
_Bool to_wire : 1;
_Bool expiring : 1;
_Bool in_progress : 1;
+ _Bool dont_cache : 1;
knot_rrset_t *rr;
};
typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
* Modules which use :c:type:`kr_request.trace_log` handler need update to modified handler API. Example migration is `modules/watchdog/watchdog.lua <https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/957/diffs#6831501329bbf9e494048fe269c6b02944fc227c>`_.
* Modules which were using logger :c:func:`kr_log_qverbose_impl` need migration to new logger :c:func:`kr_log_q`. Example migration is `modules/rebinding/rebinding.lua <https://gitlab.labs.nic.cz/knot/knot-resolver/-/merge_requests/957/diffs#6c74dcae147221ca64286a3ed028057adb6813b9>`_.
+* Modules which were using :c:func:`kr_ranked_rrarray_add` should note that on success it no longer returns exclusively zero but index into the array (non-negative). Error states are unchanged (negative).
4.x to 5.x
/* uncached entries are located at the end */
for (ssize_t i = arr->len - 1; i >= 0; --i) {
ranked_rr_array_entry_t *entry = arr->at[i];
- if (entry->qry_uid != qry->uid) {
+ if (entry->qry_uid != qry->uid || entry->dont_cache) {
continue;
- /* TODO: probably safe to break but maybe not worth it */
+ /* TODO: probably safe to break on uid mismatch but maybe not worth it */
}
int ret = stash_rrarray_entry(
arr, i, qry, cache, &unauth_cnt, nsec_pmap,
struct kr_query *qry, bool only_NS, bool is_DS);
static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl);
+static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner,
+ const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl);
static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name,
uint16_t type, uint8_t lowest_rank,
const struct kr_query *qry, struct kr_cache *cache);
KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec);
ret = answer_simple_hit(ctx, pkt, KNOT_RRTYPE_CNAME, v.data,
knot_db_val_bound(v), new_ttl);
- /* TODO: ^^ cumbersome code; we also recompute the TTL */
return ret == kr_ok() ? KR_STATE_DONE : ctx->state;
}
- case KNOT_RRTYPE_DNAME:
- VERBOSE_MSG(qry, "=> DNAME not supported yet\n"); // LATER
- return ctx->state;
+ case KNOT_RRTYPE_DNAME: {
+ const knot_db_val_t v = el[EL_DNAME];
+ assert(v.data && v.len);
+ /* TTL: for simplicity, we just ask for TTL of the generated CNAME. */
+ const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname,
+ KNOT_RRTYPE_CNAME, qry->timestamp.tv_sec);
+ ret = answer_dname_hit(ctx, pkt, k->zname, v.data,
+ knot_db_val_bound(v), new_ttl);
+ return ret == kr_ok() ? KR_STATE_DONE : ctx->state;
+ }
}
/* We have to try proving from NSEC*. */
return -ABS(ENOENT);
}
+static void answer_simple_qflags(struct kr_qflags *qf, const struct entry_h *eh,
+ uint32_t new_ttl)
+{
+ /* Finishing touches. */
+ qf->EXPIRING = is_expiring(eh->ttl, new_ttl);
+ qf->CACHED = true;
+ qf->NO_MINIMIZE = true;
+ qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE);
+ if (qf->DNSSEC_INSECURE) {
+ qf->DNSSEC_WANT = false;
+ }
+}
-static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
- const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
#define CHECK_RET(ret) do { \
if ((ret) < 0) { assert(false); return kr_error((ret)); } \
} while (false)
+
+static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type,
+ const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
{
struct kr_request *req = ctx->req;
struct kr_query *qry = req->current_query;
ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank);
CHECK_RET(ret);
- /* Finishing touches. */
- struct kr_qflags * const qf = &qry->flags;
- qf->EXPIRING = is_expiring(eh->ttl, new_ttl);
- qf->CACHED = true;
- qf->NO_MINIMIZE = true;
- qf->DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE);
- if (qf->DNSSEC_INSECURE) {
- qf->DNSSEC_WANT = false;
- }
+ answer_simple_qflags(&qry->flags, eh, new_ttl);
+
VERBOSE_MSG(qry, "=> satisfied by exact %s: rank 0%.2o, new TTL %d\n",
(type == KNOT_RRTYPE_CNAME ? "CNAME" : "RRset"),
eh->rank, new_ttl);
return kr_ok();
}
-#undef CHECK_RET
+static int answer_dname_hit(kr_layer_t *ctx, knot_pkt_t *pkt, const knot_dname_t *dname_owner,
+ const struct entry_h *eh, const void *eh_bound, uint32_t new_ttl)
+{
+ struct kr_request *req = ctx->req;
+ struct kr_query *qry = req->current_query;
+
+ /* All OK, so start constructing the (pseudo-)packet. */
+ int ret = pkt_renew(pkt, qry->sname, qry->stype);
+ CHECK_RET(ret);
+
+ /* Materialize the DNAME for the answer in (pseudo-)packet. */
+ struct answer ans;
+ memset(&ans, 0, sizeof(ans));
+ ans.mm = &pkt->mm;
+ ret = entry2answer(&ans, AR_ANSWER, eh, eh_bound,
+ dname_owner, KNOT_RRTYPE_DNAME, new_ttl);
+ CHECK_RET(ret);
+ /* Put link to the RRset into the pkt. */
+ ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank);
+ CHECK_RET(ret);
+ const knot_dname_t *dname_target =
+ knot_dname_target(ans.rrsets[AR_ANSWER].set.rr->rrs.rdata);
+
+ /* Generate CNAME RRset for the answer in (pseudo-)packet. */
+ const int AR_CNAME = AR_SOA;
+ knot_rrset_t *rr = ans.rrsets[AR_CNAME].set.rr
+ = knot_rrset_new(qry->sname, KNOT_RRTYPE_CNAME, KNOT_CLASS_IN,
+ new_ttl, ans.mm);
+ CHECK_RET(rr ? kr_ok() : -ENOMEM);
+ const knot_dname_t *cname_target = knot_dname_replace_suffix(qry->sname,
+ knot_dname_labels(dname_owner, NULL), dname_target, ans.mm);
+ CHECK_RET(cname_target ? kr_ok() : -ENOMEM);
+ const int rdata_len = knot_dname_size(cname_target);
+
+ if (rdata_len <= KNOT_DNAME_MAXLEN
+ && knot_dname_labels(cname_target, NULL) <= KNOT_DNAME_MAXLABELS) {
+ /* Normal case: the target name fits. */
+ rr->rrs.count = 1;
+ rr->rrs.size = knot_rdata_size(rdata_len);
+ rr->rrs.rdata = mm_alloc(ans.mm, rr->rrs.size);
+ CHECK_RET(rr->rrs.rdata ? kr_ok() : -ENOMEM);
+ knot_rdata_init(rr->rrs.rdata, rdata_len, cname_target);
+ /* Put link to the RRset into the pkt. */
+ ret = pkt_append(pkt, &ans.rrsets[AR_CNAME], eh->rank);
+ CHECK_RET(ret);
+ } else {
+ /* Note that it's basically a successful answer; name just doesn't fit. */
+ knot_wire_set_rcode(pkt->wire, KNOT_RCODE_YXDOMAIN);
+ }
+
+ answer_simple_qflags(&qry->flags, eh, new_ttl);
+ VERBOSE_MSG(qry, "=> satisfied by DNAME+CNAME: rank 0%.2o, new TTL %d\n",
+ eh->rank, new_ttl);
+ return kr_ok();
+}
+
+#undef CHECK_RET
/** TODO: description; see the single call site for now. */
static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val,
need_zero = false;
/* More types are possible; try in order.
* For non-fatal failures just "continue;" to try the next type. */
+ /* Now a complication - we need to try EL_DNAME before NSEC*
+ * (Unfortunately that's not easy to write very nicely.) */
+ if (!only_NS) {
+ const int i = EL_DNAME;
+ ret = check_NS_entry(k, el[i], i, exact_match, is_DS,
+ qry, timestamp);
+ if (ret < 0) goto next_label; else
+ if (!ret) {
+ /* We found our match. */
+ k->zlf_len = zlf_len;
+ return kr_ok();
+ }
+ }
const int el_count = only_NS ? EL_NS + 1 : EL_LENGTH;
for (int i = 0; i < el_count; ++i) {
+ if (i == EL_DNAME) continue;
ret = check_NS_entry(k, el[i], i, exact_match, is_DS,
qry, timestamp);
if (ret < 0) goto next_label; else
uint32_t qry_uid; /*!< Current query uid. */
uint32_t flags; /*!< Output - Flags. */
uint32_t err_cnt; /*!< Output - Number of validation failures. */
+ uint32_t cname_norrsig_cnt; /*!< Output - Number of CNAMEs missing RRSIGs. */
int result; /*!< Output - 0 or error code. */
struct {
unsigned int matching_name_type; /*!< Name + type matches */
qry->flags.FORWARD || referral);
int ret = kr_ranked_rrarray_add(&req->auth_selected, rr,
rank, to_wire, qry->uid, &req->pool);
- if (ret != kr_ok()) {
+ if (ret < 0) {
return ret;
}
}
/* CNAME was found at previous iteration, but records may not follow the correct order.
* Try to find records for pending_cname owner from section start. */
cname = pending_cname;
+ size_t cname_answ_selected_i = -1;
+ bool cname_is_occluded = false; /* whether `cname` is in a DNAME's bailiwick */
pending_cname = NULL;
const int cname_labels = knot_dname_labels(cname, NULL);
for (unsigned i = 0; i < an->count; ++i) {
/* Skip the RR if its owner+type doesn't interest us. */
const uint16_t type = kr_rrset_type_maysig(rr);
const bool type_OK = rr->type == query->stype || type == query->stype
- || type == KNOT_RRTYPE_CNAME || type == KNOT_RRTYPE_DNAME;
- /* TODO: actually handle DNAMEs */
- if (rr->rclass != KNOT_CLASS_IN || !type_OK
- || !knot_dname_is_equal(rr->owner, cname)
+ || type == KNOT_RRTYPE_CNAME;
+ if (rr->rclass != KNOT_CLASS_IN
|| knot_dname_in_bailiwick(rr->owner, query->zone_cut.name) < 0) {
continue;
}
+ const bool all_OK = type_OK && knot_dname_is_equal(rr->owner, cname);
+
+ const bool to_wire = is_final && !referral;
+
+ if (!all_OK && type == KNOT_RRTYPE_DNAME
+ && knot_dname_in_bailiwick(cname, rr->owner) >= 1) {
+ /* This DNAME (or RRSIGs) cover the current target (`cname`),
+ * so it is interesting and will occlude its CNAME.
+ * We rely on CNAME being sent along with DNAME
+ * (mandatory unless YXDOMAIN). */
+ cname_is_occluded = true;
+ uint8_t rank = get_initial_rank(rr, query, true,
+ query->flags.FORWARD || referral);
+ int ret = kr_ranked_rrarray_add(&req->answ_selected, rr,
+ rank, to_wire, query->uid, &req->pool);
+ if (ret < 0) {
+ return KR_STATE_FAIL;
+ }
+ }
+ if (!all_OK) {
+ continue;
+ }
if (rr->type == KNOT_RRTYPE_RRSIG) {
int rrsig_labels = knot_rrsig_labels(rr->rrs.rdata);
}
/* Process records matching current SNAME */
- int state = KR_STATE_FAIL;
- bool to_wire = false;
- if (is_final) {
- /* if not referral, mark record to be written to final answer */
- to_wire = !referral;
- } else {
+ if (!is_final) {
int cnt_ = 0;
- state = update_nsaddr(rr, query->parent, &cnt_);
+ int state = update_nsaddr(rr, query->parent, &cnt_);
if (state & KR_STATE_FAIL) {
return state;
}
}
uint8_t rank = get_initial_rank(rr, query, true,
query->flags.FORWARD || referral);
- state = kr_ranked_rrarray_add(&req->answ_selected, rr,
- rank, to_wire, query->uid, &req->pool);
- if (state != kr_ok()) {
+ int ret = kr_ranked_rrarray_add(&req->answ_selected, rr,
+ rank, to_wire, query->uid, &req->pool);
+ if (ret < 0) {
return KR_STATE_FAIL;
}
- /* Jump to next CNAME target */
- if ((query->stype == KNOT_RRTYPE_CNAME) || (rr->type != KNOT_RRTYPE_CNAME)) {
- continue;
- }
- pending_cname = knot_cname_name(rr->rrs.rdata);
- if (!pending_cname) {
- break;
+ cname_answ_selected_i = ret;
+
+ /* Select the next CNAME target, but don't jump immediately.
+ * There can be records for "old" cname (RRSIGs are interesting);
+ * more importantly there might be a DNAME for `cname_is_occluded`. */
+ if (query->stype != KNOT_RRTYPE_CNAME && rr->type == KNOT_RRTYPE_CNAME) {
+ pending_cname = knot_cname_name(rr->rrs.rdata);
+ if (!pending_cname) {
+ break;
+ }
}
- /* Don't use pending_cname immediately.
- * There are can be records for "old" cname. */
}
if (!pending_cname) {
break;
}
+ if (cname_is_occluded) {
+ req->answ_selected.at[cname_answ_selected_i]->dont_cache = true;
+ }
if (++(query->cname_depth) > KR_CNAME_CHAIN_LIMIT) {
VERBOSE_MSG("<= error: CNAME chain exceeded max length %d\n",
/* people count objects from 0, no CNAME = 0 */
/* KR_RANK_AUTH: we don't have the records directly from
* an authoritative source, but we do trust the server and it's
* supposed to only send us authoritative records. */
- if (err != kr_ok()) {
+ if (err < 0) {
return KR_STATE_FAIL;
}
}
case KNOT_RCODE_NOERROR:
case KNOT_RCODE_NXDOMAIN:
break; /* OK */
+ case KNOT_RCODE_YXDOMAIN: /* Basically a successful answer; name just doesn't fit. */
+ knot_wire_set_rcode(req->answer->wire, KNOT_RCODE_YXDOMAIN);
+ break;
case KNOT_RCODE_REFUSED:
case KNOT_RCODE_SERVFAIL:
if (query->flags.STUB) {
}
}
+/** Check that given CNAME could be generated by given DNAME (no DNSSEC validation). */
+static bool cname_matches_dname(const knot_rrset_t *rr_cn, const knot_rrset_t *rr_dn)
+{
+ assert(rr_cn->type == KNOT_RRTYPE_CNAME && rr_dn->type == KNOT_RRTYPE_DNAME);
+ /* When DNAME substitution happens, let's consider the "prefix"
+ * that is carried over and the "suffix" that is replaced.
+ * (Here we consider the label order used in wire and presentation.) */
+ const int prefix_labels = knot_dname_in_bailiwick(rr_cn->owner, rr_dn->owner);
+ if (prefix_labels < 1)
+ return false;
+ const knot_dname_t *cn_target = knot_cname_name(rr_cn->rrs.rdata);
+ const knot_dname_t *dn_target = knot_dname_target(rr_dn->rrs.rdata);
+ /* ^ We silently use the first RR in each RRset. Could be e.g. logged. */
+ /* Check that the suffixes are correct - and even prefix label counts. */
+ if (knot_dname_in_bailiwick(cn_target, dn_target) != prefix_labels)
+ return false;
+ /* Check that prefixes match. Find end of the first one and compare. */
+ const knot_dname_t *cn_se = rr_cn->owner;
+ for (int i = 0; i < prefix_labels; ++i)
+ cn_se += 1 + *cn_se;
+ return strncmp((const char *)rr_cn->owner, (const char *)cn_target,
+ cn_se - rr_cn->owner) == 0;
+ /* ^ We use the fact that dnames are always zero-terminated
+ * to avoid any possible over-read in cn_target. */
+}
+
static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_query *qry,
knot_mm_t *pool)
{
*/
vctx->zone_name = vctx->keys ? vctx->keys->owner : NULL;
- int validation_result = 0;
for (ssize_t i = 0; i < vctx->rrs->len; ++i) {
ranked_rr_array_entry_t *entry = vctx->rrs->at[i];
knot_rrset_t * const rr = entry->rr;
}
uint8_t rank_orig = entry->rank;
- validation_result = kr_rrset_validate(vctx, rr);
+ int validation_result = kr_rrset_validate(vctx, rr);
+
+ /* Handle the case of CNAMEs synthesized from DNAMEs (they don't have RRSIGs). */
+ if (rr->type == KNOT_RRTYPE_CNAME && validation_result == kr_error(ENOENT)) {
+ for (ssize_t j = 0; j < vctx->rrs->len; ++j) {
+ ranked_rr_array_entry_t *e_dname = vctx->rrs->at[j];
+ if ((e_dname->rr->type == KNOT_RRTYPE_DNAME)
+ /* If the order is wrong, we will need two passes. */
+ && kr_rank_test(e_dname->rank, KR_RANK_SECURE)
+ && cname_matches_dname(rr, e_dname->rr)) {
+ /* Now we believe the CNAME is OK. */
+ validation_result = kr_ok();
+ break;
+ }
+ }
+ if (validation_result != kr_ok()) {
+ vctx->cname_norrsig_cnt += 1;
+ }
+ }
+
if (validation_result == kr_ok()) {
kr_rank_set(&entry->rank, KR_RANK_SECURE);
.has_nsec3 = has_nsec3,
.flags = 0,
.err_cnt = 0,
+ .cname_norrsig_cnt = 0,
.result = 0
};
int ret = validate_section(&vctx, qry, pool);
+ if (vctx.err_cnt && vctx.err_cnt == vctx.cname_norrsig_cnt) {
+ VERBOSE_MSG(qry, ">< all validation errors are missing RRSIGs on CNAMES, trying again in hope for DNAMEs\n");
+ vctx.err_cnt = vctx.cname_norrsig_cnt = vctx.result = 0;
+ ret = validate_section(&vctx, qry, pool);
+ }
req->answ_validated = (vctx.err_cnt == 0);
if (ret != kr_ok()) {
return ret;
abort();
}
}
- return kr_ok();
+ return i;
}
/* No stashed rrset found, add */
if (!entry) {
return kr_error(ENOMEM);
}
+ memset(entry, 0, sizeof(*entry)); /* default all to zeros */
knot_rrset_t *rr_new = knot_rrset_new(rr->owner, rr->type, rr->rclass, rr->ttl, pool);
if (!rr_new) {
entry->qry_uid = qry_uid;
entry->rr = rr_new;
entry->rank = rank;
- entry->revalidation_cnt = 0;
- entry->cached = false;
- entry->yielded = false;
entry->to_wire = to_wire;
entry->in_progress = true;
if (array_push(*array, entry) < 0) {
return kr_error(ENOMEM);
}
- return to_wire_ensure_unique(array, array->len - 1);
+ ret = to_wire_ensure_unique(array, array->len - 1);
+ if (ret < 0) return ret;
+ return array->len - 1;
}
/** Comparator for qsort() on an array of knot_data_t pointers. */
bool to_wire : 1; /**< whether to be put into the answer */
bool expiring : 1; /**< low remaining TTL; see is_expiring; only used in cache ATM */
bool in_progress : 1; /**< build of RRset in progress, i.e. different format of RR data */
+ bool dont_cache : 1; /**< avoid caching; useful e.g. for generated data */
knot_rrset_t *rr;
};
typedef struct ranked_rr_array_entry ranked_rr_array_entry_t;
*
* To convert to standard RRs inside, you need to call _finalize() afterwards,
* and the memory of rr->rrs.rdata has to remain until then.
+ *
+ * \return array index (>= 0) or error code (< 0)
*/
KR_EXPORT
int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,