diff --git a/NEWS b/NEWS index 6584df92aa4bf92c172c801df4bed099056a9c3b..bc43716e097ee36eca83dc7bb694fc0fdb641e79 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,7 @@ Bugfixes - 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) diff --git a/daemon/lua/kres-gen.lua b/daemon/lua/kres-gen.lua index 5b9b69275f56da1caa1680e6e4d4987b8bba812e..853d23d7a3a4874eb56972b63fb84a4b5c5a7a0a 100644 --- a/daemon/lua/kres-gen.lua +++ b/daemon/lua/kres-gen.lua @@ -129,6 +129,7 @@ struct ranked_rr_array_entry { _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; diff --git a/doc/upgrading.rst b/doc/upgrading.rst index e35a264ff91c530f016c53f85dfef7ed868fc59d..b1b3a004e467ca6d5909eb4bb72b3649703186ca 100644 --- a/doc/upgrading.rst +++ b/doc/upgrading.rst @@ -18,6 +18,7 @@ Module changes * 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 diff --git a/lib/cache/api.c b/lib/cache/api.c index fcf81374323146fe55e43712a418c5c8f2d557b7..17da2c10618690f8742f492a2f7ed5103c0a84fc 100644 --- a/lib/cache/api.c +++ b/lib/cache/api.c @@ -378,9 +378,9 @@ int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) /* 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, diff --git a/lib/cache/peek.c b/lib/cache/peek.c index 16bfb9ff787e05ca235246b96c97cd50dcafffb1..0d66bb6303253cc7efa25e135014a489f84809bf 100644 --- a/lib/cache/peek.c +++ b/lib/cache/peek.c @@ -16,6 +16,8 @@ static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el, 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); @@ -161,12 +163,18 @@ int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) 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*. */ @@ -405,12 +413,25 @@ static int peek_encloser( 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; @@ -430,22 +451,71 @@ static int answer_simple_hit(kr_layer_t *ctx, knot_pkt_t *pkt, uint16_t type, 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, @@ -619,8 +689,22 @@ static int closest_NS(struct kr_cache *cache, struct key *k, entry_list_t el, 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 diff --git a/lib/dnssec.h b/lib/dnssec.h index 170b3a87f20b31d1c4077eee2838c3c7e0014993..d9601eaa998ebb9b0fbafcf1a9030ddfc21941c9 100644 --- a/lib/dnssec.h +++ b/lib/dnssec.h @@ -45,6 +45,7 @@ struct kr_rrset_validation_ctx { 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 */ diff --git a/lib/layer/iterate.c b/lib/layer/iterate.c index 6558c50b7de02431ad0e6a7825ba35f153942500..3d2a93cb20583248b90663da3f984cd5287b7060 100644 --- a/lib/layer/iterate.c +++ b/lib/layer/iterate.c @@ -374,7 +374,7 @@ static int pick_authority(knot_pkt_t *pkt, struct kr_request *req, bool to_wire) 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; } } @@ -498,6 +498,8 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, /* 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) { @@ -506,13 +508,33 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, /* 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); @@ -528,39 +550,38 @@ static int unroll_cname(knot_pkt_t *pkt, struct kr_request *req, bool referral, } /* 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 */ @@ -828,7 +849,7 @@ static int process_stub(knot_pkt_t *pkt, struct kr_request *req) /* 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; } } @@ -1068,6 +1089,9 @@ static int resolve(kr_layer_t *ctx, knot_pkt_t *pkt) 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) { diff --git a/lib/layer/validate.c b/lib/layer/validate.c index c60858b430566c7be008d9bc67be3d64b9977d23..2fa89d23df7e096a62b6769ad2cf79e303a2eeed 100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@ -82,6 +82,32 @@ static void log_bogus_rrsig(kr_rrset_validation_ctx_t *vctx, const struct kr_que } } +/** 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) { @@ -94,7 +120,6 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_que */ 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; @@ -121,7 +146,26 @@ static int validate_section(kr_rrset_validation_ctx_t *vctx, const struct kr_que } 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); @@ -172,10 +216,16 @@ static int validate_records(struct kr_request *req, knot_pkt_t *answer, knot_mm_ .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; diff --git a/lib/utils.c b/lib/utils.c index 2755d65f66b1c7cd808407f612769212989605c0..2be3ca60bc6fb9b233286a66f01eb602854c132b 100644 --- a/lib/utils.c +++ b/lib/utils.c @@ -755,7 +755,7 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, abort(); } } - return kr_ok(); + return i; } /* No stashed rrset found, add */ @@ -768,6 +768,7 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, 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) { @@ -780,9 +781,6 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, 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) { @@ -792,7 +790,9 @@ int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr, 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. */ diff --git a/lib/utils.h b/lib/utils.h index 886ef871102320579b9f55747dcaae2e7289d9d1..55e280fdf233c741afd3e5cfedea2d4ff07c1522 100644 --- a/lib/utils.h +++ b/lib/utils.h @@ -204,6 +204,7 @@ struct ranked_rr_array_entry { 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; @@ -420,6 +421,8 @@ int kr_rrkey(char *key, uint16_t class, const knot_dname_t *owner, * * 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,