/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> 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 <http://www.gnu.org/licenses/>. */ #include <libknot/descriptor.h> #include <libknot/errcode.h> #include <libknot/rrset.h> #include <libknot/internal/mempool.h> #include <libknot/rrtype/rdname.h> #include <libknot/rrtype/rrsig.h> #include "lib/layer/iterate.h" #include "lib/cache.h" #include "lib/module.h" #define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), "rrsc", fmt) #define DEBUG_MSG_NOPLAN(fmt...) QRDEBUG(NULL, "rrsc", fmt) //#define DEBUG_MSG(fmt...) //#define DEBUG_MSG_NOPLAN(fmt...) static int begin(knot_layer_t *ctx, void *module_param) { ctx->data = module_param; return ctx->state; } /** @internal Baton for stash_commit */ struct stash_baton { struct kr_cache_txn *txn; unsigned timestamp; }; static int commit_rrsig(const char *key, void *val, void *data) { knot_rrset_t *rrsig = val; struct stash_baton *baton = data; if (knot_rrset_ttl(rrsig) < 1) { return kr_ok(); /* Ignore cache busters. */ } /* Check if already cached */ /** @todo This should check if less trusted data is in the cache, for that the cache would need to trace data trust level. */ unsigned drift = baton->timestamp; knot_rrset_t query_rrsig; knot_rrset_init(&query_rrsig, rrsig->owner, rrsig->type, rrsig->rclass); if (kr_cache_peek_rrsig(baton->txn, &query_rrsig, &drift) == 0) { return kr_ok(); } return kr_cache_insert_rrsig(baton->txn, rrsig, rrsig->type, baton->timestamp); } static int stash_commit(map_t *stash, unsigned timestamp, struct kr_cache_txn *txn) { struct stash_baton baton = { .txn = txn, .timestamp = timestamp }; return map_walk(stash, &commit_rrsig, &baton); return kr_ok(); } static int validate_rrsig(const knot_rrset_t *rrsig, size_t pos, const knot_rrset_t *rr, struct kr_cache_txn *txn, struct kr_request *req) { struct kr_rplan *rplan = &req->rplan; struct kr_query *qry = kr_rplan_current(rplan); uint32_t timestamp = qry->timestamp.tv_sec; const knot_dname_t *signer = knot_rrsig_signer_name(&rrsig->rrs, pos); knot_rrset_t dnskey_rr; knot_rrset_init(&dnskey_rr, (knot_dname_t *)signer, KNOT_RRTYPE_DNSKEY, rr->rclass); int ret = kr_cache_peek_rr(txn, &dnskey_rr, ×tamp); if (ret != 0) { return KNOT_DNSSEC_ENOKEY; } return kr_ok(); } static int merge_in_rrsigs(knot_rrset_t *cache_rr, const knot_rrset_t *rrsigset, const knot_rrset_t *rr, struct kr_cache_txn *txn, struct kr_request *req) { struct kr_rplan *rplan = &req->rplan; struct kr_query *query = kr_rplan_current(&req->rplan); int ret = KNOT_EOK; /* Find rrset corresponding to RRSIG. */ for (unsigned i = 0; i < rrsigset->rrs.rr_count; ++i) { if ((knot_rrsig_type_covered(&rrsigset->rrs, i) == rr->type) && knot_dname_is_equal(cache_rr->owner, rrsigset->owner)) { ret = validate_rrsig(rrsigset, i, rr, txn, req); switch (ret) { case KNOT_DNSSEC_ENOKEY: #ifndef NDEBUG { char name_str[KNOT_DNAME_MAXLEN], type_str[16]; knot_dname_to_str(name_str, knot_rrsig_signer_name(&rrsigset->rrs, i), sizeof(name_str)); knot_rrtype_to_string(rr->type, type_str, sizeof(type_str)); DEBUG_MSG_NOPLAN("%s() Missing DNSKEY for '%s %s'.\n", __func__, type_str, name_str); } #endif DEBUG_MSG("<= missing DNSKEY\n"); const knot_dname_t *signer = knot_rrsig_signer_name(&rrsigset->rrs, i); struct kr_query *next = kr_rplan_push(rplan, query->parent, signer, query->sclass, KNOT_RRTYPE_DNSKEY); if (!next) { return KNOT_STATE_FAIL; } rem_node(&query->node); /* *MUST* keep current query at tail */ insert_node(&query->node, &next->node); next->flags |= QUERY_AWAIT_CUT; return ret; break; case 0: break; default: return ret; break; } const knot_rdata_t *rdata = knot_rdataset_at(&rrsigset->rrs, i); ret = knot_rdataset_add(&cache_rr->rrs, rdata, rplan->pool); if (KNOT_EOK != ret) { return ret; } } } return ret; } static int scan_for_rrsigs(knot_rrset_t *cache_rrsig, const knot_pktsection_t *section, const knot_rrset_t *rr, struct kr_request *req) { struct kr_rplan *rplan = &req->rplan; struct kr_cache *cache = &req->ctx->cache; struct kr_cache_txn txn; int ret = kr_cache_txn_begin(cache, &txn, 0); if (0 != ret) { return ret; } knot_rrset_init(cache_rrsig, rr->owner, rr->type, rr->rclass); for (uint16_t i = 0; i < section->count; ++i) { const knot_rrset_t *rrset = knot_pkt_rr(section, i); if (KNOT_RRTYPE_RRSIG != rrset->type) { continue; } /* Currently only merge. Signature check is missing. */ int ret = merge_in_rrsigs(cache_rrsig, rrset, rr, &txn, req); if (KNOT_EOK != ret) { knot_rrset_clear(cache_rrsig, rplan->pool); kr_cache_txn_abort(&txn); return ret; } } kr_cache_txn_abort(&txn); return kr_ok(); } static int stash_add_rrsig(map_t *stash, const knot_pktsection_t *section, const knot_rrset_t *rr, struct kr_request *req) { if (rr->type == KNOT_RRTYPE_RRSIG) { return kr_ok(); } struct kr_rplan *rplan = &req->rplan; /* Stash key = {[1-255] owner, [1-5] type covered, [1] \x00 } */ char key[8 + KNOT_DNAME_MAXLEN]; int ret = knot_dname_to_wire((uint8_t *)key, rr->owner, KNOT_DNAME_MAXLEN); if (ret <= 0) { return ret; } knot_dname_to_lower((uint8_t *)key); ret = snprintf(key + ret - 1, sizeof(key) - KNOT_DNAME_MAXLEN, "%hu", rr->type); if (ret <= 0 || ret >= KNOT_DNAME_MAXLEN) { return kr_error(EILSEQ); } /* Check if already exists */ knot_rrset_t *stashed = map_get(stash, key); if (stashed) { return kr_ok(); } /* Construct RRSIG RRSet containing related data. */ knot_rrset_t cache_rrsig; ret = scan_for_rrsigs(&cache_rrsig, section, rr, req); if (ret != 0) { return ret; } if (cache_rrsig.rrs.rr_count) { stashed = knot_rrset_copy(&cache_rrsig, rplan->pool); if (!stashed) { return kr_error(ENOMEM); } } knot_rrset_clear(&cache_rrsig, rplan->pool); if (stashed) { return map_set(stash, key, stashed); } return kr_ok(); } static void stash_glue(map_t *stash, knot_pkt_t *pkt, const knot_dname_t *ns_name, mm_ctx_t *pool) { const knot_pktsection_t *additional = knot_pkt_section(pkt, KNOT_ADDITIONAL); for (unsigned i = 0; i < additional->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(additional, i); if ((rr->type != KNOT_RRTYPE_A && rr->type != KNOT_RRTYPE_AAAA) || !knot_dname_is_equal(rr->owner, ns_name)) { continue; } // stash_add_rrsig(stash, additional, rr, pool); } } static int stash_authority(map_t *stash, knot_pkt_t *pkt, struct kr_request *req) { struct kr_rplan *rplan = &req->rplan; struct kr_query *qry = kr_rplan_current(rplan); const knot_pktsection_t *authority = knot_pkt_section(pkt, KNOT_AUTHORITY); for (unsigned i = 0; i < authority->count; ++i) { const knot_rrset_t *rr = knot_pkt_rr(authority, i); /* Cache in-bailiwick data only */ if (!knot_dname_in(qry->zone_cut.name, rr->owner)) { continue; } /* Look up glue records for NS */ if (rr->type == KNOT_RRTYPE_NS) { stash_glue(stash, pkt, knot_ns_name(&rr->rrs, 0), rplan->pool); } /* Ignore RRSIGs directly. */ if (rr->type == KNOT_RRTYPE_RRSIG) { continue; } /* Stash record */ int ret = stash_add_rrsig(stash, authority, rr, req); if (ret == KNOT_DNSSEC_ENOKEY) { return ret; } } return kr_ok(); } static int stash_answer(map_t *stash, knot_pkt_t *pkt, struct kr_request *req) { struct kr_rplan *rplan = &req->rplan; struct kr_query *qry = kr_rplan_current(rplan); const knot_dname_t *cname = qry->sname; const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER); for (unsigned i = 0; i < answer->count; ++i) { /* Stash direct answers (equal to current QNAME/CNAME) */ const knot_rrset_t *rr = knot_pkt_rr(answer, i); if (!knot_dname_is_equal(rr->owner, cname)) { continue; } /* Ignore RRSIGs directly. */ if (rr->type == KNOT_RRTYPE_RRSIG) { continue; } int ret = stash_add_rrsig(stash, answer, rr, req); if (ret == KNOT_DNSSEC_ENOKEY) { return ret; } /* Follow CNAME chain */ if (rr->type == KNOT_RRTYPE_CNAME) { cname = knot_cname_name(&rr->rrs); } else { cname = qry->sname; } } return kr_ok(); } static int scan_rrsigs(map_t *stash, knot_pkt_t *pkt, struct kr_request *req) { int ret = stash_authority(stash, pkt, req); if (ret == 0 && knot_wire_get_aa(pkt->wire)) { ret = stash_answer(stash, pkt, req); } return ret; } static int stash(knot_layer_t *ctx, knot_pkt_t *pkt) { /* Do nothing when no DNSSEC required. */ bool dobit = knot_pkt_has_dnssec(pkt); if (!dobit) { return ctx->state; } struct kr_request *req = ctx->data; struct kr_rplan *rplan = &req->rplan; #ifndef NDEBUG { struct kr_query *qry = NULL; WALK_LIST(qry, rplan->resolved) { char qname_str[KNOT_DNAME_MAXLEN], qtype_str[16]; knot_dname_to_str(qname_str, qry->sname, sizeof(qname_str)); knot_rrtype_to_string(qry->stype, qtype_str, sizeof(qtype_str)); DEBUG_MSG("%s() resolved '%s %s' at %u\n", __func__, qtype_str, qname_str, qry->timestamp.tv_sec); } } #endif struct kr_query *qry = kr_rplan_current(rplan); if (!qry || ctx->state & KNOT_STATE_FAIL) { return ctx->state; } /* Cache only positive answers. */ if (qry->flags & QUERY_CACHED || knot_wire_get_rcode(pkt->wire) != KNOT_RCODE_NOERROR) { return ctx->state; } /* Stash RRSIG data from the AUTHORITY and ANSWER. */ map_t stash_rrsig = map_make(); stash_rrsig.malloc = (map_alloc_f) mm_alloc; stash_rrsig.free = (map_free_f) mm_free; stash_rrsig.baton = rplan->pool; int ret = scan_rrsigs(&stash_rrsig, pkt, req); /* Cache stashed records */ if (ret == 0) { /* Open write transaction */ struct kr_cache *cache = &req->ctx->cache; struct kr_cache_txn txn; if (kr_cache_txn_begin(cache, &txn, 0) == 0) { ret = stash_commit(&stash_rrsig, qry->timestamp.tv_sec, &txn); if (ret == 0) { kr_cache_txn_commit(&txn); } else { kr_cache_txn_abort(&txn); } } /* Clear if full */ if (ret == KNOT_ESPACE) { /* * Commit empty transaction to make freed pages reclaimable * (This increases the txnid) */ if (kr_cache_txn_begin(cache, &txn, 0) == 0) { kr_cache_txn_commit(&txn); } /* Now drop the database */ if (kr_cache_txn_begin(cache, &txn, 0) == 0) { ret = kr_cache_clear(&txn); if (ret == 0) { kr_cache_txn_commit(&txn); } else { kr_cache_txn_abort(&txn); } } } } return ctx->state; } /** Module implementation. */ const knot_layer_api_t *rrsigcache_layer(struct kr_module *module) { static const knot_layer_api_t _layer = { .begin = &begin, .consume = &stash }; return &_layer; } KR_MODULE_EXPORT(rrsigcache)