From: Vladimír Čunát Date: Tue, 12 Jun 2018 09:06:50 +0000 (+0200) Subject: separate most of code for retrieval from cache X-Git-Tag: v2.4.0~19^2~12 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2d249ea784a203e0b84b85013ebb13a61c0c249c;p=thirdparty%2Fknot-resolver.git separate most of code for retrieval from cache api.c was growing too long. Also a few other minor changes. --- diff --git a/lib/cache/api.c b/lib/cache/api.c index b52dcd0e1..64e8704b3 100644 --- a/lib/cache/api.c +++ b/lib/cache/api.c @@ -32,9 +32,7 @@ #include "lib/cache/api.h" #include "lib/cache/cdb_lmdb.h" #include "lib/defines.h" -#include "lib/dnssec/ta.h" #include "lib/generic/trie.h" -#include "lib/layer/iterate.h" #include "lib/resolve.h" #include "lib/rplan.h" #include "lib/utils.h" @@ -297,37 +295,9 @@ knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type) return (knot_db_val_t){ k->buf + 1, name_len + 4 }; } -/** Like key_exact_type_maypkt but with extra checks if used for RRs only. */ -static knot_db_val_t key_exact_type(struct key *k, uint16_t type) -{ - switch (type) { - /* Sanity check: forbidden types represented in other way(s). */ - case KNOT_RRTYPE_NSEC: - case KNOT_RRTYPE_NSEC3: - assert(false); - return (knot_db_val_t){ NULL, 0 }; - } - return key_exact_type_maypkt(k, type); -} - - -// TODO: move all these and cache_peek_real into a separate c-file that only exposes cache_peek_real -/* Forwards for larger chunks of code. All just for cache_peek. */ -static uint8_t get_lowest_rank(const struct kr_request *req, const struct kr_query *qry); -static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val, - uint8_t lowest_rank); -static int closest_NS(kr_layer_t *ctx, struct key *k, entry_list_t el); -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 peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt); -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); - -static int peek_encloser( - struct key *k, struct answer *ans, int sname_labels, - uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache); +/** The inside for cache_peek(); implementation separated to ./peek.c */ +int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt); /** function for .produce phase */ int cache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) { @@ -359,329 +329,7 @@ int cache_peek(kr_layer_t *ctx, knot_pkt_t *pkt) return ret; } -static int nsec_p_init(struct nsec_p *nsec_p, const uint8_t *nsec_p_raw) -{ - nsec_p->raw = nsec_p_raw; - if (!nsec_p_raw) return kr_ok(); - nsec_p->hash = nsec_p_mkHash(nsec_p->raw); - /* Convert NSEC3 params to another format. */ - const dnssec_binary_t rdata = { - .size = nsec_p_rdlen(nsec_p->raw), - .data = (uint8_t *)/*const-cast*/nsec_p->raw, - }; - int ret = dnssec_nsec3_params_from_rdata(&nsec_p->libknot, &rdata); - return ret == DNSSEC_EOK ? kr_ok() : kr_error(ret); -} - -static void nsec_p_cleanup(struct nsec_p *nsec_p) -{ - dnssec_binary_free(&nsec_p->libknot.salt); - /* We don't really need to clear it, but it's not large. (`salt` zeroed above) */ - memset(nsec_p, 0, sizeof(*nsec_p)); -} - -/** - * \note we don't transition to KR_STATE_FAIL even in case of "unexpected errors". - */ -static int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) -{ - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - struct kr_cache *cache = &req->ctx->cache; - - struct key k_storage, *k = &k_storage; - int ret = kr_dname_lf(k->buf, qry->sname, false); - if (unlikely(ret)) { - assert(false); - return ctx->state; - } - - const uint8_t lowest_rank = get_lowest_rank(req, qry); - - /**** 1. find the name or the closest (available) zone, not considering wildcards - **** 1a. exact name+type match (can be negative answer in insecure zones) */ - knot_db_val_t key = key_exact_type_maypkt(k, qry->stype); - knot_db_val_t val = { NULL, 0 }; - ret = cache_op(cache, read, &key, &val, 1); - if (!ret) { - VERBOSE_MSG(qry, "=> read OK\n"); - /* found an entry: test conditions, materialize into pkt, etc. */ - ret = found_exact_hit(ctx, pkt, val, lowest_rank); - } - if (ret && ret != -abs(ENOENT)) { - VERBOSE_MSG(qry, "=> exact hit error: %d %s\n", ret, kr_strerror(ret)); - assert(false); - return ctx->state; - } else if (!ret) { - return KR_STATE_DONE; - } - - /**** 1b. otherwise, find the longest prefix zone/xNAME (with OK time+rank). [...] */ - k->zname = qry->sname; - ret = kr_dname_lf(k->buf, k->zname, false); /* LATER(optim.): probably remove */ - if (unlikely(ret)) { - assert(false); - return ctx->state; - } - entry_list_t el; - ret = closest_NS(ctx, k, el); - if (ret) { - assert(ret == kr_error(ENOENT)); - if (ret != kr_error(ENOENT) || !el[0].len) { - return ctx->state; - } - } - switch (k->type) { - case KNOT_RRTYPE_CNAME: { - const knot_db_val_t v = el[EL_CNAME]; - assert(v.data && v.len); - const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname, - 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; - } - - /* We have to try proving from NSEC*. */ - WITH_VERBOSE(qry) { - auto_free char *zname_str = kr_dname_text(k->zname); - if (!el[0].len) { - VERBOSE_MSG(qry, "=> no NSEC* cached for zone: %s\n", zname_str); - } else { - VERBOSE_MSG(qry, "=> trying zone: %s (first %s)\n", zname_str, - (el[0].len == 4 ? "NSEC" : "NSEC3")); - } - } - -#if 0 - if (!eh) { /* fall back to root hints? */ - ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); - if (ret) return ctx->state; - assert(!qry->zone_cut.parent); - - //VERBOSE_MSG(qry, "=> using root hints\n"); - //qry->flags.AWAIT_CUT = false; - return ctx->state; - } - - /* Now `eh` points to the closest NS record that we've found, - * and that's the only place to start - we may either find - * a negative proof or we may query upstream from that point. */ - kr_zonecut_set(&qry->zone_cut, k->zname); - ret = kr_make_query(qry, pkt); // TODO: probably not yet - qname minimization - if (ret) return ctx->state; -#endif - - /** Structure for collecting multiple NSEC* + RRSIG records, - * in preparation for the answer, and for tracking the progress. */ - struct answer ans; - memset(&ans, 0, sizeof(ans)); - ans.mm = &pkt->mm; - const int sname_labels = knot_dname_labels(qry->sname, NULL); - - /* Try the NSEC* parameters in order, until success. - * Let's not mix different parameters for NSEC* RRs in a single proof. */ - for (int i = 0; ;) { - if (!el[i].len) goto cont; - /* OK iff the stamp is in future */ - uint32_t stamp; - memcpy(&stamp, el[i].data, sizeof(stamp)); - const int32_t remains = stamp - qry->timestamp.tv_sec; /* using SOA serial arith. */ - if (remains < 0) goto cont; - { - const uint8_t *nsec_p_raw = el[i].len > sizeof(stamp) - ? (uint8_t *)el[i].data + sizeof(stamp) : NULL; - nsec_p_init(&ans.nsec_p, nsec_p_raw); - } - /**** 2. and 3. inside */ - ret = peek_encloser(k, &ans, sname_labels, - lowest_rank, qry, cache); - nsec_p_cleanup(&ans.nsec_p); - if (!ret) break; - if (ret < 0) return ctx->state; - cont: - /* Otherwise we try another nsec_p, if available. */ - if (++i == ENTRY_APEX_NSECS_CNT) return ctx->state; - /* clear possible partial answers in `ans` (no need to deallocate) */ - ans.rcode = 0; - memset(&ans.rrsets, 0, sizeof(ans.rrsets)); - } - - /**** 4. add SOA iff needed */ - if (ans.rcode != PKT_NOERROR) { - /* Assuming k->buf still starts with zone's prefix, - * look up the SOA in cache. */ - k->buf[0] = k->zlf_len; - key = key_exact_type(k, KNOT_RRTYPE_SOA); - knot_db_val_t val = { NULL, 0 }; - ret = cache_op(cache, read, &key, &val, 1); - const struct entry_h *eh; - if (ret || !(eh = entry_h_consistent(val, KNOT_RRTYPE_SOA))) { - assert(ret); /* only want to catch `eh` failures */ - VERBOSE_MSG(qry, "=> SOA missed\n"); - return ctx->state; - } - /* Check if the record is OK. */ - int32_t new_ttl = get_new_ttl(eh, qry, k->zname, KNOT_RRTYPE_SOA, - qry->timestamp.tv_sec); - if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { - VERBOSE_MSG(qry, "=> SOA unfit %s: rank 0%.2o, new TTL %d\n", - (eh->is_packet ? "packet" : "RR"), - eh->rank, new_ttl); - return ctx->state; - } - /* Add the SOA into the answer. */ - ret = entry2answer(&ans, AR_SOA, eh, knot_db_val_bound(val), - k->zname, KNOT_RRTYPE_SOA, new_ttl); - if (ret) return ctx->state; - } - - /* Find our target RCODE. */ - int real_rcode; - switch (ans.rcode) { - case PKT_NODATA: - case PKT_NOERROR: /* positive wildcarded response */ - real_rcode = KNOT_RCODE_NOERROR; - break; - case PKT_NXDOMAIN: - real_rcode = KNOT_RCODE_NXDOMAIN; - break; - default: - assert(false); - case 0: /* i.e. nothing was found */ - /* LATER(optim.): zone cut? */ - VERBOSE_MSG(qry, "=> cache miss\n"); - return ctx->state; - } - - if (pkt_renew(pkt, qry->sname, qry->stype) - || knot_pkt_begin(pkt, KNOT_ANSWER) - ) { - assert(false); - return ctx->state; - } - knot_wire_set_rcode(pkt->wire, real_rcode); - - - bool expiring = false; // TODO - VERBOSE_MSG(qry, "=> writing RRsets: "); - for (int i = 0; i < sizeof(ans.rrsets) / sizeof(ans.rrsets[0]); ++i) { - if (i == 1) knot_pkt_begin(pkt, KNOT_AUTHORITY); - if (!ans.rrsets[i].set.rr) continue; - expiring = expiring || ans.rrsets[i].set.expiring; - ret = pkt_append(pkt, &ans.rrsets[i], ans.rrsets[i].set.rank); - if (ret) { - assert(false); - return ctx->state; - } - kr_log_verbose(kr_rank_test(ans.rrsets[i].set.rank, KR_RANK_SECURE) - ? "+" : "-"); - } - kr_log_verbose("\n"); - /* Finishing touches. */ - qry->flags.EXPIRING = expiring; - qry->flags.CACHED = true; - qry->flags.NO_MINIMIZE = true; - - return KR_STATE_DONE; -} - -/** - * This is where the high-level "business logic" of aggressive cache is. - * \return 0: success (may need SOA); >0: try other nsec_p; <0: exit cache immediately. - */ -static int peek_encloser( - struct key *k, struct answer *ans, const int sname_labels, - uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache) -{ - /** Start of NSEC* covering the sname; - * it's part of key - the one within zone (read only) */ - knot_db_val_t cover_low_kwz = { NULL, 0 }; - knot_dname_t cover_hi_storage[KNOT_DNAME_MAXLEN]; - /** End of NSEC* covering the sname. */ - knot_db_val_t cover_hi_kwz = { - .data = cover_hi_storage, - .len = sizeof(cover_hi_storage), - }; - - /**** 2. Find a closest (provable) encloser (of sname). */ - int clencl_labels = -1; - bool clencl_is_tentative = false; - if (!ans->nsec_p.raw) { /* NSEC */ - int ret = nsec1_encloser(k, ans, sname_labels, &clencl_labels, - &cover_low_kwz, &cover_hi_kwz, qry, cache); - if (ret) return ret; - } else { - int ret = nsec3_encloser(k, ans, sname_labels, &clencl_labels, - qry, cache); - clencl_is_tentative = ret == ABS(ENOENT) && clencl_labels >= 0; - /* ^^ Last chance: *positive* wildcard record under this clencl. */ - if (ret && !clencl_is_tentative) return ret; - } - /* We should have either a match or a cover at this point. */ - if (ans->rcode != PKT_NODATA && ans->rcode != PKT_NXDOMAIN) { - assert(false); - return kr_error(EINVAL); - } - const bool ncloser_covered = ans->rcode == PKT_NXDOMAIN; - - /** Name of the closest (provable) encloser. */ - const knot_dname_t *clencl_name = qry->sname; - for (int l = sname_labels; l > clencl_labels; --l) - clencl_name = knot_wire_next_label(clencl_name, NULL); - - /**** 3. source of synthesis checks, in case the next closer name was covered. - **** 3a. We want to query for NSEC* of source of synthesis (SS) or its - * predecessor, providing us with a proof of its existence or non-existence. */ - if (ncloser_covered && !ans->nsec_p.raw) { - int ret = nsec1_src_synth(k, ans, clencl_name, - cover_low_kwz, cover_hi_kwz, qry, cache); - if (ret == AR_SOA) return 0; - assert(ret <= 0); - if (ret) return ret; - - } else if (ncloser_covered && ans->nsec_p.raw && !clencl_is_tentative) { - int ret = nsec3_src_synth(k, ans, clencl_name, qry, cache); - if (ret == AR_SOA) return 0; - assert(ret <= 0); - if (ret) return ret; - - } /* else (!ncloser_covered) so no wildcard checks needed, - * as we proved that sname exists. */ - - /**** 3b. find wildcarded answer, if next closer name was covered - * and we don't have a full proof yet. (common for NSEC*) */ - if (!ncloser_covered) - return kr_ok(); /* decrease indentation */ - /* Construct key for exact qry->stype + source of synthesis. */ - int ret = kr_dname_lf(k->buf, clencl_name, true); - if (ret) { - assert(!ret); - return kr_error(ret); - } - const uint16_t types[] = { qry->stype, KNOT_RRTYPE_CNAME }; - for (int i = 0; i < (2 - (qry->stype == KNOT_RRTYPE_CNAME)); ++i) { - ret = try_wild(k, ans, clencl_name, types[i], - lowest_rank, qry, cache); - if (ret == kr_ok()) { - return kr_ok(); - } else if (ret != -ABS(ENOENT) && ret != -ABS(ESTALE)) { - assert(false); - return kr_error(ret); - } - /* else continue */ - } - /* Neither attempt succeeded, but the NSEC* proofs were found, - * so skip trying other parameters, as it seems very unlikely - * to turn out differently than by the same wildcard search. */ - return -ABS(ENOENT); -} /** It's simply inside of cycle taken out to decrease indentation. \return error code. */ static int stash_rrarray_entry(ranked_rr_array_t *arr, int arr_i, @@ -691,7 +339,7 @@ static int stash_rrarray_entry(ranked_rr_array_t *arr, int arr_i, static int stash_nsec_p(const knot_dname_t *dname, const char *nsec_p_v, struct kr_request *req); - +/** The whole .consume phase for the cache module. */ int cache_stash(kr_layer_t *ctx, knot_pkt_t *pkt) { struct kr_request *req = ctx->req; @@ -787,7 +435,6 @@ static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry, const int wild_labels = rr_sigs == NULL ? 0 : knot_dname_labels(rr->owner, NULL) - knot_rrsig_labels(&rr_sigs->rrs, 0); - //kr_log_verbose("wild_labels = %d\n", wild_labels); if (wild_labels < 0) { return kr_ok(); } @@ -887,6 +534,7 @@ static ssize_t stash_rrset(struct kr_cache *cache, const struct kr_query *qry, if (rdataset_dematerialize(&rr->rrs, eh->data) || rdataset_dematerialize(rds_sigs, eh->data + rr_ssize)) { /* minimize the damage from incomplete write; TODO: better */ + eh->time = 0; eh->ttl = 0; eh->rank = 0; assert(false); @@ -1065,139 +713,6 @@ static int stash_nsec_p(const knot_dname_t *dname, const char *nsec_p_v, return kr_ok(); } -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) -{ - 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 sets 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, - qry->sname, type, new_ttl); - CHECK_RET(ret); - /* Put links to the materialized data into the pkt. */ - ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank); - CHECK_RET(ret); - /* Finishing touches. */ - qry->flags.EXPIRING = is_expiring(eh->ttl, new_ttl); - qry->flags.CACHED = true; - qry->flags.NO_MINIMIZE = true; - qry->flags.DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE); - if (qry->flags.DNSSEC_INSECURE) { - qry->flags.DNSSEC_WANT = false; - } - 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 - - -/** 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, - uint8_t lowest_rank) -{ - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - - int ret = entry_h_seek(&val, qry->stype); - if (ret) return ret; - VERBOSE_MSG(qry, "=> FEH seek OK \n"); - const struct entry_h *eh = entry_h_consistent(val, qry->stype); - if (!eh) { - assert(false); - return kr_error(ENOENT); - // LATER: recovery in case of error, perhaps via removing the entry? - // LATER(optim): pehaps optimize the zone cut search - } - VERBOSE_MSG(qry, "=> FEH consistent OK \n"); - - int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, qry->stype, - qry->timestamp.tv_sec); - if (new_ttl < 0 || eh->rank < lowest_rank) { - /* Positive record with stale TTL or bad rank. - * LATER(optim.): It's unlikely that we find a negative one, - * so we might theoretically skip all the cache code. */ - - VERBOSE_MSG(qry, "=> skipping exact %s: rank 0%.2o (min. 0%.2o), new TTL %d\n", - eh->is_packet ? "packet" : "RR", eh->rank, lowest_rank, new_ttl); - return kr_error(ENOENT); - } - - const uint8_t *eh_bound = knot_db_val_bound(val); - if (eh->is_packet) { - /* Note: we answer here immediately, even if it's (theoretically) - * possible that we could generate a higher-security negative proof. - * Rank is high-enough so we take it to save time searching. */ - return answer_from_pkt (ctx, pkt, qry->stype, eh, eh_bound, new_ttl); - } else { - return answer_simple_hit(ctx, pkt, qry->stype, eh, eh_bound, new_ttl); - } -} - - -/** Try to satisfy via wildcard (positively). See the single call site. */ -static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, - const uint16_t type, const uint8_t lowest_rank, - const struct kr_query *qry, struct kr_cache *cache) -{ - knot_db_val_t key = key_exact_type(k, type); - /* Find the record. */ - knot_db_val_t val = { NULL, 0 }; - int ret = cache_op(cache, read, &key, &val, 1); - if (!ret) { - ret = entry_h_seek(&val, type); - } - if (ret) { - if (ret != -ABS(ENOENT)) { - VERBOSE_MSG(qry, "=> wildcard: hit error %d %s\n", - ret, strerror(abs(ret))); - assert(false); - } - WITH_VERBOSE(qry) { - auto_free char *clencl_str = kr_dname_text(clencl_name), - *type_str = kr_rrtype_text(type); - VERBOSE_MSG(qry, "=> wildcard: not found: *.%s %s\n", - clencl_str, type_str); - } - return ret; - } - /* Check if the record is OK. */ - const struct entry_h *eh = entry_h_consistent(val, type); - if (!eh) { - assert(false); - return kr_error(ret); - // LATER: recovery in case of error, perhaps via removing the entry? - } - int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, type, qry->timestamp.tv_sec); - /* ^^ here we use the *expanded* wildcard name */ - if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { - /* Wildcard record with stale TTL, bad rank or packet. */ - VERBOSE_MSG(qry, "=> wildcard: skipping %s, rank 0%.2o, new TTL %d\n", - eh->is_packet ? "packet" : "RR", eh->rank, new_ttl); - return -ABS(ESTALE); - } - /* Add the RR into the answer. */ - ret = entry2answer(ans, AR_ANSWER, eh, knot_db_val_bound(val), - qry->sname, type, new_ttl); - VERBOSE_MSG(qry, "=> wildcard: answer expanded, ret = %d, new TTL %d\n", - ret, (int)new_ttl); - if (ret) return kr_error(ret); - ans->rcode = PKT_NOERROR; - return kr_ok(); -} - static int peek_exact_real(struct kr_cache *cache, const knot_dname_t *name, uint16_t type, struct kr_cache_p *peek) @@ -1245,143 +760,3 @@ int kr_cache_peek_exact(struct kr_cache *cache, const knot_dname_t *name, uint16 return ret; } -/** Find the longest prefix zone/xNAME (with OK time+rank), starting at k->*. - * We store xNAME at NS type to lower the number of searches. - * CNAME is only considered for equal name, of course. - * We also store NSEC* parameters at NS type; probably the latest two will be kept. - * Found type is returned via k->type. - * - * \return raw entry from cache (for NS) or rewound entry (xNAME) FIXME - */ -static int closest_NS(kr_layer_t *ctx, struct key *k, entry_list_t el) -{ - struct kr_request *req = ctx->req; - struct kr_query *qry = req->current_query; - struct kr_cache *cache = &req->ctx->cache; - - int zlf_len = k->buf[0]; - - uint8_t rank_min = KR_RANK_INSECURE | KR_RANK_AUTH; - // LATER(optim): if stype is NS, we check the same value again - bool exact_match = true; - bool need_zero = true; - /* Inspect the NS/xNAME entries, shortening by a label on each iteration. */ - do { - k->buf[0] = zlf_len; - knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS); - knot_db_val_t val; - int ret = cache_op(cache, read, &key, &val, 1); - if (ret == -abs(ENOENT)) goto next_label; - if (ret) { - assert(!ret); - if (need_zero) memset(el, 0, sizeof(entry_list_t)); - return kr_error(ret); - } - - /* Check consistency, find any type; - * using `goto` for shortening by another label. */ - ret = entry_list_parse(val, el); - if (ret) { - assert(!ret); // do something about it? - goto next_label; - } - need_zero = false; - /* More types are possible; try in order. - * For non-fatal failures just "continue;" to try the next type. */ - for (int i = ENTRY_APEX_NSECS_CNT; i < EL_LENGTH; ++i) { - if (!el[i].len - /* On a zone cut we want DS from the parent zone. */ - || (i == EL_NS && exact_match && qry->stype == KNOT_RRTYPE_DS) - /* CNAME is interesting only if we - * directly hit the name that was asked. - * Note that we want it even in the DS case. */ - || (i == EL_CNAME && !exact_match) - /* DNAME is interesting only if we did NOT - * directly hit the name that was asked. */ - || (i == EL_DNAME && exact_match) - ) { - continue; - } - /* ^^ LATER(optim.): not having NS but having - * non-timeouted nsec_p is also OK for a zone cut. */ - /* Find the entry for the type, check positivity, TTL */ - const uint16_t type = EL2RRTYPE(i); - const struct entry_h *eh = entry_h_consistent(el[i], type); - if (!eh) { - VERBOSE_MSG(qry, "=> EH seek ret: %d\n", ret); - assert(false); - goto next_label; - } - int32_t new_ttl = get_new_ttl(eh, qry, k->zname, type, - qry->timestamp.tv_sec); - if (new_ttl < 0 - /* Not interested in negative or bogus. */ - || eh->is_packet - /* For NS any kr_rank is accepted, - * as insecure or even nonauth is OK */ - || (type != KNOT_RRTYPE_NS && eh->rank < rank_min)) { - - WITH_VERBOSE(qry) { - auto_free char *type_str = - kr_rrtype_text(type); - const char *packet_str = - eh->is_packet ? "packet" : "RR"; - VERBOSE_MSG(qry, "=> skipping unfit %s %s: " - "rank 0%.2o, new TTL %d\n", - type_str, packet_str, - eh->rank, new_ttl); - } - continue; - } - /* We found our match. */ - k->type = type; - k->zlf_len = zlf_len; - return kr_ok(); - } - - next_label: - /* remove one more label */ - exact_match = false; - if (k->zname[0] == 0) { - /* We miss root NS in cache, but let's at least assume it exists. */ - k->type = KNOT_RRTYPE_NS; - k->zlf_len = zlf_len; - assert(zlf_len == 0); - if (need_zero) memset(el, 0, sizeof(entry_list_t)); - return kr_error(ENOENT); - } - zlf_len -= (k->zname[0] + 1); - k->zname += (k->zname[0] + 1); - k->buf[zlf_len + 1] = 0; - } while (true); -} - - -static uint8_t get_lowest_rank(const struct kr_request *req, const struct kr_query *qry) -{ - /* TODO: move rank handling into the iterator (DNSSEC_* flags)? */ - const bool allow_unverified = - knot_wire_get_cd(req->answer->wire) || qry->flags.STUB; - /* in stub mode we don't trust RRs anyway ^^ */ - if (qry->flags.NONAUTH) { - return KR_RANK_INITIAL; - /* Note: there's little sense in validation status for non-auth records. - * In case of using NONAUTH to get NS IPs, knowing that you ask correct - * IP doesn't matter much for security; it matters whether you can - * validate the answers from the NS. - */ - } else if (!allow_unverified) { - /* Records not present under any TA don't have their security - * verified at all, so we also accept low ranks in that case. */ - const bool ta_covers = kr_ta_covers_qry(req->ctx, qry->sname, qry->stype); - /* ^ TODO: performance? TODO: stype - call sites */ - if (ta_covers) { - return KR_RANK_INSECURE | KR_RANK_AUTH; - } /* else falltrhough */ - } - return KR_RANK_INITIAL | KR_RANK_AUTH; -} - - - - diff --git a/lib/cache/impl.h b/lib/cache/impl.h index 7d95e10a9..0a1aff1de 100644 --- a/lib/cache/impl.h +++ b/lib/cache/impl.h @@ -139,6 +139,18 @@ static const int NSEC3_HASH_LEN = 20, */ knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type); +/** Like key_exact_type_maypkt but with extra checks if used for RRs only. */ +static inline knot_db_val_t key_exact_type(struct key *k, uint16_t type) +{ + switch (type) { + /* Sanity check: forbidden types represented in other way(s). */ + case KNOT_RRTYPE_NSEC: + case KNOT_RRTYPE_NSEC3: + assert(false); + return (knot_db_val_t){ NULL, 0 }; + } + return key_exact_type_maypkt(k, type); +} /* entry_h chaining; implementation in ./entry_list.c */ diff --git a/lib/cache/peek.c b/lib/cache/peek.c new file mode 100644 index 000000000..3bea5c3a9 --- /dev/null +++ b/lib/cache/peek.c @@ -0,0 +1,632 @@ +/* Copyright (C) 2018 CZ.NIC, z.s.p.o. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#include "lib/cache/impl.h" + +#include "lib/dnssec/ta.h" +#include "lib/layer/iterate.h" + +/* The whole file only exports peek_nosync(). + * Forwards for larger chunks of code: */ + +static int found_exact_hit(kr_layer_t *ctx, knot_pkt_t *pkt, knot_db_val_t val, + uint8_t lowest_rank); +static int closest_NS(kr_layer_t *ctx, struct key *k, entry_list_t el); +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 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); + +static int peek_encloser( + struct key *k, struct answer *ans, int sname_labels, + uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache); + + +static int nsec_p_init(struct nsec_p *nsec_p, const uint8_t *nsec_p_raw) +{ + nsec_p->raw = nsec_p_raw; + if (!nsec_p_raw) return kr_ok(); + nsec_p->hash = nsec_p_mkHash(nsec_p->raw); + /* Convert NSEC3 params to another format. */ + const dnssec_binary_t rdata = { + .size = nsec_p_rdlen(nsec_p->raw), + .data = (uint8_t *)/*const-cast*/nsec_p->raw, + }; + int ret = dnssec_nsec3_params_from_rdata(&nsec_p->libknot, &rdata); + return ret == DNSSEC_EOK ? kr_ok() : kr_error(ret); +} + +static void nsec_p_cleanup(struct nsec_p *nsec_p) +{ + dnssec_binary_free(&nsec_p->libknot.salt); + /* We don't really need to clear it, but it's not large. (`salt` zeroed above) */ + memset(nsec_p, 0, sizeof(*nsec_p)); +} + +static uint8_t get_lowest_rank(const struct kr_request *req, const struct kr_query *qry) +{ + /* TODO: move rank handling into the iterator (DNSSEC_* flags)? */ + const bool allow_unverified = + knot_wire_get_cd(req->answer->wire) || qry->flags.STUB; + /* in stub mode we don't trust RRs anyway ^^ */ + if (qry->flags.NONAUTH) { + return KR_RANK_INITIAL; + /* Note: there's little sense in validation status for non-auth records. + * In case of using NONAUTH to get NS IPs, knowing that you ask correct + * IP doesn't matter much for security; it matters whether you can + * validate the answers from the NS. + */ + } else if (!allow_unverified) { + /* Records not present under any TA don't have their security + * verified at all, so we also accept low ranks in that case. */ + const bool ta_covers = kr_ta_covers_qry(req->ctx, qry->sname, qry->stype); + /* ^ TODO: performance? TODO: stype - call sites */ + if (ta_covers) { + return KR_RANK_INSECURE | KR_RANK_AUTH; + } /* else falltrhough */ + } + return KR_RANK_INITIAL | KR_RANK_AUTH; +} + + +/** Almost whole .produce phase for the cache module. + * \note we don't transition to KR_STATE_FAIL even in case of "unexpected errors". + */ +int peek_nosync(kr_layer_t *ctx, knot_pkt_t *pkt) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + struct kr_cache *cache = &req->ctx->cache; + + struct key k_storage, *k = &k_storage; + int ret = kr_dname_lf(k->buf, qry->sname, false); + if (unlikely(ret)) { + assert(false); + return ctx->state; + } + + const uint8_t lowest_rank = get_lowest_rank(req, qry); + + /**** 1. find the name or the closest (available) zone, not considering wildcards + **** 1a. exact name+type match (can be negative answer in insecure zones) */ + knot_db_val_t key = key_exact_type_maypkt(k, qry->stype); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + if (!ret) { + VERBOSE_MSG(qry, "=> read OK\n"); + /* found an entry: test conditions, materialize into pkt, etc. */ + ret = found_exact_hit(ctx, pkt, val, lowest_rank); + } + if (ret && ret != -abs(ENOENT)) { + VERBOSE_MSG(qry, "=> exact hit error: %d %s\n", ret, kr_strerror(ret)); + assert(false); + return ctx->state; + } else if (!ret) { + return KR_STATE_DONE; + } + + /**** 1b. otherwise, find the longest prefix zone/xNAME (with OK time+rank). [...] */ + k->zname = qry->sname; + ret = kr_dname_lf(k->buf, k->zname, false); /* LATER(optim.): probably remove */ + if (unlikely(ret)) { + assert(false); + return ctx->state; + } + entry_list_t el; + ret = closest_NS(ctx, k, el); + if (ret) { + assert(ret == kr_error(ENOENT)); + if (ret != kr_error(ENOENT) || !el[0].len) { + return ctx->state; + } + } + switch (k->type) { + case KNOT_RRTYPE_CNAME: { + const knot_db_val_t v = el[EL_CNAME]; + assert(v.data && v.len); + const int32_t new_ttl = get_new_ttl(v.data, qry, qry->sname, + 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; + } + + /* We have to try proving from NSEC*. */ + WITH_VERBOSE(qry) { + auto_free char *zname_str = kr_dname_text(k->zname); + if (!el[0].len) { + VERBOSE_MSG(qry, "=> no NSEC* cached for zone: %s\n", zname_str); + } else { + VERBOSE_MSG(qry, "=> trying zone: %s (first %s)\n", zname_str, + (el[0].len == 4 ? "NSEC" : "NSEC3")); + } + } + +#if 0 + if (!eh) { /* fall back to root hints? */ + ret = kr_zonecut_set_sbelt(req->ctx, &qry->zone_cut); + if (ret) return ctx->state; + assert(!qry->zone_cut.parent); + + //VERBOSE_MSG(qry, "=> using root hints\n"); + //qry->flags.AWAIT_CUT = false; + return ctx->state; + } + + /* Now `eh` points to the closest NS record that we've found, + * and that's the only place to start - we may either find + * a negative proof or we may query upstream from that point. */ + kr_zonecut_set(&qry->zone_cut, k->zname); + ret = kr_make_query(qry, pkt); // TODO: probably not yet - qname minimization + if (ret) return ctx->state; +#endif + + /** Structure for collecting multiple NSEC* + RRSIG records, + * in preparation for the answer, and for tracking the progress. */ + struct answer ans; + memset(&ans, 0, sizeof(ans)); + ans.mm = &pkt->mm; + const int sname_labels = knot_dname_labels(qry->sname, NULL); + + /* Try the NSEC* parameters in order, until success. + * Let's not mix different parameters for NSEC* RRs in a single proof. */ + for (int i = 0; ;) { + if (!el[i].len) goto cont; + /* OK iff the stamp is in future */ + uint32_t stamp; + memcpy(&stamp, el[i].data, sizeof(stamp)); + const int32_t remains = stamp - qry->timestamp.tv_sec; /* using SOA serial arith. */ + if (remains < 0) goto cont; + { + const uint8_t *nsec_p_raw = el[i].len > sizeof(stamp) + ? (uint8_t *)el[i].data + sizeof(stamp) : NULL; + nsec_p_init(&ans.nsec_p, nsec_p_raw); + } + /**** 2. and 3. inside */ + ret = peek_encloser(k, &ans, sname_labels, + lowest_rank, qry, cache); + nsec_p_cleanup(&ans.nsec_p); + if (!ret) break; + if (ret < 0) return ctx->state; + cont: + /* Otherwise we try another nsec_p, if available. */ + if (++i == ENTRY_APEX_NSECS_CNT) return ctx->state; + /* clear possible partial answers in `ans` (no need to deallocate) */ + ans.rcode = 0; + memset(&ans.rrsets, 0, sizeof(ans.rrsets)); + } + + /**** 4. add SOA iff needed */ + if (ans.rcode != PKT_NOERROR) { + /* Assuming k->buf still starts with zone's prefix, + * look up the SOA in cache. */ + k->buf[0] = k->zlf_len; + key = key_exact_type(k, KNOT_RRTYPE_SOA); + knot_db_val_t val = { NULL, 0 }; + ret = cache_op(cache, read, &key, &val, 1); + const struct entry_h *eh; + if (ret || !(eh = entry_h_consistent(val, KNOT_RRTYPE_SOA))) { + assert(ret); /* only want to catch `eh` failures */ + VERBOSE_MSG(qry, "=> SOA missed\n"); + return ctx->state; + } + /* Check if the record is OK. */ + int32_t new_ttl = get_new_ttl(eh, qry, k->zname, KNOT_RRTYPE_SOA, + qry->timestamp.tv_sec); + if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { + VERBOSE_MSG(qry, "=> SOA unfit %s: rank 0%.2o, new TTL %d\n", + (eh->is_packet ? "packet" : "RR"), + eh->rank, new_ttl); + return ctx->state; + } + /* Add the SOA into the answer. */ + ret = entry2answer(&ans, AR_SOA, eh, knot_db_val_bound(val), + k->zname, KNOT_RRTYPE_SOA, new_ttl); + if (ret) return ctx->state; + } + + /* Find our target RCODE. */ + int real_rcode; + switch (ans.rcode) { + case PKT_NODATA: + case PKT_NOERROR: /* positive wildcarded response */ + real_rcode = KNOT_RCODE_NOERROR; + break; + case PKT_NXDOMAIN: + real_rcode = KNOT_RCODE_NXDOMAIN; + break; + default: + assert(false); + case 0: /* i.e. nothing was found */ + /* LATER(optim.): zone cut? */ + VERBOSE_MSG(qry, "=> cache miss\n"); + return ctx->state; + } + + if (pkt_renew(pkt, qry->sname, qry->stype) + || knot_pkt_begin(pkt, KNOT_ANSWER) + ) { + assert(false); + return ctx->state; + } + knot_wire_set_rcode(pkt->wire, real_rcode); + + bool expiring = false; // TODO + VERBOSE_MSG(qry, "=> writing RRsets: "); + for (int i = 0; i < sizeof(ans.rrsets) / sizeof(ans.rrsets[0]); ++i) { + if (i == 1) knot_pkt_begin(pkt, KNOT_AUTHORITY); + if (!ans.rrsets[i].set.rr) continue; + expiring = expiring || ans.rrsets[i].set.expiring; + ret = pkt_append(pkt, &ans.rrsets[i], ans.rrsets[i].set.rank); + if (ret) { + assert(false); + return ctx->state; + } + kr_log_verbose(kr_rank_test(ans.rrsets[i].set.rank, KR_RANK_SECURE) + ? "+" : "-"); + } + kr_log_verbose("\n"); + /* Finishing touches. */ + qry->flags.EXPIRING = expiring; + qry->flags.CACHED = true; + qry->flags.NO_MINIMIZE = true; + + return KR_STATE_DONE; +} + +/** + * This is where the high-level "business logic" of aggressive cache is. + * \return 0: success (may need SOA); >0: try other nsec_p; <0: exit cache immediately. + */ +static int peek_encloser( + struct key *k, struct answer *ans, const int sname_labels, + uint8_t lowest_rank, const struct kr_query *qry, struct kr_cache *cache) +{ + /** Start of NSEC* covering the sname; + * it's part of key - the one within zone (read only) */ + knot_db_val_t cover_low_kwz = { NULL, 0 }; + knot_dname_t cover_hi_storage[KNOT_DNAME_MAXLEN]; + /** End of NSEC* covering the sname. */ + knot_db_val_t cover_hi_kwz = { + .data = cover_hi_storage, + .len = sizeof(cover_hi_storage), + }; + + /**** 2. Find a closest (provable) encloser (of sname). */ + int clencl_labels = -1; + bool clencl_is_tentative = false; + if (!ans->nsec_p.raw) { /* NSEC */ + int ret = nsec1_encloser(k, ans, sname_labels, &clencl_labels, + &cover_low_kwz, &cover_hi_kwz, qry, cache); + if (ret) return ret; + } else { + int ret = nsec3_encloser(k, ans, sname_labels, &clencl_labels, + qry, cache); + clencl_is_tentative = ret == ABS(ENOENT) && clencl_labels >= 0; + /* ^^ Last chance: *positive* wildcard record under this clencl. */ + if (ret && !clencl_is_tentative) return ret; + } + + /* We should have either a match or a cover at this point. */ + if (ans->rcode != PKT_NODATA && ans->rcode != PKT_NXDOMAIN) { + assert(false); + return kr_error(EINVAL); + } + const bool ncloser_covered = ans->rcode == PKT_NXDOMAIN; + + /** Name of the closest (provable) encloser. */ + const knot_dname_t *clencl_name = qry->sname; + for (int l = sname_labels; l > clencl_labels; --l) + clencl_name = knot_wire_next_label(clencl_name, NULL); + + /**** 3. source of synthesis checks, in case the next closer name was covered. + **** 3a. We want to query for NSEC* of source of synthesis (SS) or its + * predecessor, providing us with a proof of its existence or non-existence. */ + if (ncloser_covered && !ans->nsec_p.raw) { + int ret = nsec1_src_synth(k, ans, clencl_name, + cover_low_kwz, cover_hi_kwz, qry, cache); + if (ret == AR_SOA) return 0; + assert(ret <= 0); + if (ret) return ret; + + } else if (ncloser_covered && ans->nsec_p.raw && !clencl_is_tentative) { + int ret = nsec3_src_synth(k, ans, clencl_name, qry, cache); + if (ret == AR_SOA) return 0; + assert(ret <= 0); + if (ret) return ret; + + } /* else (!ncloser_covered) so no wildcard checks needed, + * as we proved that sname exists. */ + + /**** 3b. find wildcarded answer, if next closer name was covered + * and we don't have a full proof yet. (common for NSEC*) */ + if (!ncloser_covered) + return kr_ok(); /* decrease indentation */ + /* Construct key for exact qry->stype + source of synthesis. */ + int ret = kr_dname_lf(k->buf, clencl_name, true); + if (ret) { + assert(!ret); + return kr_error(ret); + } + const uint16_t types[] = { qry->stype, KNOT_RRTYPE_CNAME }; + for (int i = 0; i < (2 - (qry->stype == KNOT_RRTYPE_CNAME)); ++i) { + ret = try_wild(k, ans, clencl_name, types[i], + lowest_rank, qry, cache); + if (ret == kr_ok()) { + return kr_ok(); + } else if (ret != -ABS(ENOENT) && ret != -ABS(ESTALE)) { + assert(false); + return kr_error(ret); + } + /* else continue */ + } + /* Neither attempt succeeded, but the NSEC* proofs were found, + * so skip trying other parameters, as it seems very unlikely + * to turn out differently than by the same wildcard search. */ + return -ABS(ENOENT); +} + + +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) +{ + 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 sets 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, + qry->sname, type, new_ttl); + CHECK_RET(ret); + /* Put links to the materialized data into the pkt. */ + ret = pkt_append(pkt, &ans.rrsets[AR_ANSWER], eh->rank); + CHECK_RET(ret); + /* Finishing touches. */ + qry->flags.EXPIRING = is_expiring(eh->ttl, new_ttl); + qry->flags.CACHED = true; + qry->flags.NO_MINIMIZE = true; + qry->flags.DNSSEC_INSECURE = kr_rank_test(eh->rank, KR_RANK_INSECURE); + if (qry->flags.DNSSEC_INSECURE) { + qry->flags.DNSSEC_WANT = false; + } + 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 + + +/** 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, + uint8_t lowest_rank) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + + int ret = entry_h_seek(&val, qry->stype); + if (ret) return ret; + VERBOSE_MSG(qry, "=> FEH seek OK \n"); + const struct entry_h *eh = entry_h_consistent(val, qry->stype); + if (!eh) { + assert(false); + return kr_error(ENOENT); + // LATER: recovery in case of error, perhaps via removing the entry? + // LATER(optim): pehaps optimize the zone cut search + } + VERBOSE_MSG(qry, "=> FEH consistent OK \n"); + + int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, qry->stype, + qry->timestamp.tv_sec); + if (new_ttl < 0 || eh->rank < lowest_rank) { + /* Positive record with stale TTL or bad rank. + * LATER(optim.): It's unlikely that we find a negative one, + * so we might theoretically skip all the cache code. */ + + VERBOSE_MSG(qry, "=> skipping exact %s: rank 0%.2o (min. 0%.2o), new TTL %d\n", + eh->is_packet ? "packet" : "RR", eh->rank, lowest_rank, new_ttl); + return kr_error(ENOENT); + } + + const uint8_t *eh_bound = knot_db_val_bound(val); + if (eh->is_packet) { + /* Note: we answer here immediately, even if it's (theoretically) + * possible that we could generate a higher-security negative proof. + * Rank is high-enough so we take it to save time searching. */ + return answer_from_pkt (ctx, pkt, qry->stype, eh, eh_bound, new_ttl); + } else { + return answer_simple_hit(ctx, pkt, qry->stype, eh, eh_bound, new_ttl); + } +} + + +/** Try to satisfy via wildcard (positively). See the single call site. */ +static int try_wild(struct key *k, struct answer *ans, const knot_dname_t *clencl_name, + const uint16_t type, const uint8_t lowest_rank, + const struct kr_query *qry, struct kr_cache *cache) +{ + knot_db_val_t key = key_exact_type(k, type); + /* Find the record. */ + knot_db_val_t val = { NULL, 0 }; + int ret = cache_op(cache, read, &key, &val, 1); + if (!ret) { + ret = entry_h_seek(&val, type); + } + if (ret) { + if (ret != -ABS(ENOENT)) { + VERBOSE_MSG(qry, "=> wildcard: hit error %d %s\n", + ret, strerror(abs(ret))); + assert(false); + } + WITH_VERBOSE(qry) { + auto_free char *clencl_str = kr_dname_text(clencl_name), + *type_str = kr_rrtype_text(type); + VERBOSE_MSG(qry, "=> wildcard: not found: *.%s %s\n", + clencl_str, type_str); + } + return ret; + } + /* Check if the record is OK. */ + const struct entry_h *eh = entry_h_consistent(val, type); + if (!eh) { + assert(false); + return kr_error(ret); + // LATER: recovery in case of error, perhaps via removing the entry? + } + int32_t new_ttl = get_new_ttl(eh, qry, qry->sname, type, qry->timestamp.tv_sec); + /* ^^ here we use the *expanded* wildcard name */ + if (new_ttl < 0 || eh->rank < lowest_rank || eh->is_packet) { + /* Wildcard record with stale TTL, bad rank or packet. */ + VERBOSE_MSG(qry, "=> wildcard: skipping %s, rank 0%.2o, new TTL %d\n", + eh->is_packet ? "packet" : "RR", eh->rank, new_ttl); + return -ABS(ESTALE); + } + /* Add the RR into the answer. */ + ret = entry2answer(ans, AR_ANSWER, eh, knot_db_val_bound(val), + qry->sname, type, new_ttl); + VERBOSE_MSG(qry, "=> wildcard: answer expanded, ret = %d, new TTL %d\n", + ret, (int)new_ttl); + if (ret) return kr_error(ret); + ans->rcode = PKT_NOERROR; + return kr_ok(); +} + +/** Find the longest prefix zone/xNAME (with OK time+rank), starting at k->*. + * We store xNAME at NS type to lower the number of searches. + * CNAME is only considered for equal name, of course. + * We also store NSEC* parameters at NS type; probably the latest two will be kept. + * Found type is returned via k->type. + * + * \return raw entry from cache (for NS) or rewound entry (xNAME) FIXME + */ +static int closest_NS(kr_layer_t *ctx, struct key *k, entry_list_t el) +{ + struct kr_request *req = ctx->req; + struct kr_query *qry = req->current_query; + struct kr_cache *cache = &req->ctx->cache; + + int zlf_len = k->buf[0]; + + uint8_t rank_min = KR_RANK_INSECURE | KR_RANK_AUTH; + // LATER(optim): if stype is NS, we check the same value again + bool exact_match = true; + bool need_zero = true; + /* Inspect the NS/xNAME entries, shortening by a label on each iteration. */ + do { + k->buf[0] = zlf_len; + knot_db_val_t key = key_exact_type(k, KNOT_RRTYPE_NS); + knot_db_val_t val; + int ret = cache_op(cache, read, &key, &val, 1); + if (ret == -abs(ENOENT)) goto next_label; + if (ret) { + assert(!ret); + if (need_zero) memset(el, 0, sizeof(entry_list_t)); + return kr_error(ret); + } + + /* Check consistency, find any type; + * using `goto` for shortening by another label. */ + ret = entry_list_parse(val, el); + if (ret) { + assert(!ret); // do something about it? + goto next_label; + } + need_zero = false; + /* More types are possible; try in order. + * For non-fatal failures just "continue;" to try the next type. */ + for (int i = ENTRY_APEX_NSECS_CNT; i < EL_LENGTH; ++i) { + if (!el[i].len + /* On a zone cut we want DS from the parent zone. */ + || (i == EL_NS && exact_match && qry->stype == KNOT_RRTYPE_DS) + /* CNAME is interesting only if we + * directly hit the name that was asked. + * Note that we want it even in the DS case. */ + || (i == EL_CNAME && !exact_match) + /* DNAME is interesting only if we did NOT + * directly hit the name that was asked. */ + || (i == EL_DNAME && exact_match) + ) { + continue; + } + /* ^^ LATER(optim.): not having NS but having + * non-timeouted nsec_p is also OK for a zone cut. */ + /* Find the entry for the type, check positivity, TTL */ + const uint16_t type = EL2RRTYPE(i); + const struct entry_h *eh = entry_h_consistent(el[i], type); + if (!eh) { + VERBOSE_MSG(qry, "=> EH seek ret: %d\n", ret); + assert(false); + goto next_label; + } + int32_t new_ttl = get_new_ttl(eh, qry, k->zname, type, + qry->timestamp.tv_sec); + if (new_ttl < 0 + /* Not interested in negative or bogus. */ + || eh->is_packet + /* For NS any kr_rank is accepted, + * as insecure or even nonauth is OK */ + || (type != KNOT_RRTYPE_NS && eh->rank < rank_min)) { + + WITH_VERBOSE(qry) { + auto_free char *type_str = + kr_rrtype_text(type); + const char *packet_str = + eh->is_packet ? "packet" : "RR"; + VERBOSE_MSG(qry, "=> skipping unfit %s %s: " + "rank 0%.2o, new TTL %d\n", + type_str, packet_str, + eh->rank, new_ttl); + } + continue; + } + /* We found our match. */ + k->type = type; + k->zlf_len = zlf_len; + return kr_ok(); + } + + next_label: + /* remove one more label */ + exact_match = false; + if (k->zname[0] == 0) { + /* We miss root NS in cache, but let's at least assume it exists. */ + k->type = KNOT_RRTYPE_NS; + k->zlf_len = zlf_len; + assert(zlf_len == 0); + if (need_zero) memset(el, 0, sizeof(entry_list_t)); + return kr_error(ENOENT); + } + zlf_len -= (k->zname[0] + 1); + k->zname += (k->zname[0] + 1); + k->buf[zlf_len + 1] = 0; + } while (true); +} + diff --git a/lib/lib.mk b/lib/lib.mk index 4e9efb273..933059cb2 100644 --- a/lib/lib.mk +++ b/lib/lib.mk @@ -7,6 +7,7 @@ libkres_SOURCES := \ lib/cache/knot_pkt.c \ lib/cache/nsec1.c \ lib/cache/nsec3.c \ + lib/cache/peek.c \ lib/dnssec.c \ lib/dnssec/nsec.c \ lib/dnssec/nsec3.c \