From: Marek VavruĊĦa Date: Thu, 23 Jul 2015 14:54:41 +0000 (+0200) Subject: Merge branch 'master' into cache-rrsig-wip X-Git-Tag: v1.0.0-beta1~53^2~134 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=baea9e65caa53d4e22eb98d63763eb2286ff7d6d;p=thirdparty%2Fknot-resolver.git Merge branch 'master' into cache-rrsig-wip Conflicts: lib/layer/rrcache.c lib/resolve.c lib/resolve.h lib/rplan.h lib/zonecut.c modules/stats/stats.c --- baea9e65caa53d4e22eb98d63763eb2286ff7d6d diff --cc lib/layer/pktcache.c index af91ef84a,eb62571cb..dcd8ce920 --- a/lib/layer/pktcache.c +++ b/lib/layer/pktcache.c @@@ -26,9 -26,9 +26,9 @@@ #define DEFAULT_MAXTTL (15 * 60) #define DEFAULT_NOTTL (5) /* Short-time "no data" retention to avoid bursts */ -static inline uint8_t get_tag(knot_pkt_t *pkt) +static inline uint8_t get_tag(struct kr_request *req) { - return (req->flags & KR_REQ_DNSSEC) ? KR_CACHE_SEC : KR_CACHE_PKT; - return knot_pkt_has_dnssec(pkt) ? KR_CACHE_SEC : KR_CACHE_PKT; ++ return (req->options & QUERY_DNSSEC_WANT) ? KR_CACHE_SEC : KR_CACHE_PKT; } static int begin(knot_layer_t *ctx, void *module_param) diff --cc lib/layer/rrcache.c index 6ef46da5f,9d017caf6..6710c0ea6 --- a/lib/layer/rrcache.c +++ b/lib/layer/rrcache.c @@@ -27,24 -26,24 +27,31 @@@ #include "lib/module.h" #define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), " rc ", fmt) + #define DEFAULT_MINTTL (5) /* Short-time "no data" retention to avoid bursts */ +/* Stash key flags */ +#define KEY_FLAG_NO 0x01 +#define KEY_FLAG_RRSIG 0x02 +#define KEY_FLAG_SET(key, flag) key[0] = (flag); +#define KEY_COVERING_RRSIG(key) (key[0] & KEY_FLAG_RRSIG) + static int begin(knot_layer_t *ctx, void *module_param) { ctx->data = module_param; return ctx->state; } + /** Record is expiring if it has less than 1% TTL (or less than 5s) */ + static inline bool is_expiring(const knot_rrset_t *rr, uint32_t drift) + { + return 100 * (drift + 5) > 99 * knot_rrset_ttl(rr); + } + static int loot_rr(struct kr_cache_txn *txn, knot_pkt_t *pkt, const knot_dname_t *name, - uint16_t rrclass, uint16_t rrtype, struct kr_query *qry) + uint16_t rrclass, uint16_t rrtype, struct kr_query *qry, bool fetch_rrsig) { /* Check if record exists in cache */ + int ret = 0; uint32_t drift = qry->timestamp.tv_sec; knot_rrset_t cache_rr; knot_rrset_init(&cache_rr, (knot_dname_t *)name, rrtype, rrclass); @@@ -120,7 -113,7 +131,7 @@@ static int peek(knot_layer_t *ctx, knot * Only one step of the chain is resolved at a time. */ struct kr_cache *cache = &req->ctx->cache; - int ret = loot_cache(cache, pkt, qry, req->flags & KR_REQ_DNSSEC); - int ret = loot_cache(cache, pkt, qry); ++ int ret = loot_cache(cache, pkt, qry, req->options & QUERY_DNSSEC_WANT); if (ret == 0) { DEBUG_MSG("=> satisfied from cache\n"); qry->flags |= QUERY_CACHED|QUERY_NO_MINIMIZE; @@@ -135,40 -128,24 +146,48 @@@ /** @internal Baton for stash_commit */ struct stash_baton { + struct kr_request *req; struct kr_cache_txn *txn; unsigned timestamp; + uint32_t min_ttl; }; +static int commit_rrsig(struct stash_baton *baton, knot_rrset_t *rr) +{ + /* If not doing secure resolution, ignore (unvalidated) RRSIGs. */ - if (!(baton->req->flags & KR_REQ_DNSSEC)) { ++ if (!(baton->req->options & QUERY_DNSSEC_WANT)) { + return kr_ok(); + } + /* Commit covering RRSIG to a separate cache namespace. */ + uint16_t covered = knot_rrsig_type_covered(&rr->rrs, 0); + unsigned drift = baton->timestamp; + knot_rrset_t query_rrsig; + knot_rrset_init(&query_rrsig, rr->owner, covered, rr->rclass); + if (kr_cache_peek_rrsig(baton->txn, &query_rrsig, &drift) == 0) { + return kr_ok(); + } + return kr_cache_insert_rrsig(baton->txn, rr, covered, baton->timestamp); +} + static int commit_rr(const char *key, void *val, void *data) { knot_rrset_t *rr = val; struct stash_baton *baton = data; - if (knot_rrset_ttl(rr) < 1) { - return kr_ok(); /* Ignore cache busters */ + /* Ensure minimum TTL */ + knot_rdata_t *rd = rr->rrs.data; + for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) { + if (knot_rdata_ttl(rd) < baton->min_ttl) { + knot_rdata_set_ttl(rd, baton->min_ttl); + } + rd = kr_rdataset_next(rd); } + + /* Save RRSIG in a special cache. */ + unsigned drift = baton->timestamp; + if (KEY_COVERING_RRSIG(key)) { + return commit_rrsig(baton, rr); + } ++ /* 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. @@@ -181,12 -162,12 +203,13 @@@ return kr_cache_insert_rr(baton->txn, rr, baton->timestamp); } -static int stash_commit(map_t *stash, unsigned timestamp, struct kr_cache_txn *txn) +static int stash_commit(map_t *stash, unsigned timestamp, struct kr_cache_txn *txn, struct kr_request *req) { struct stash_baton baton = { + .req = req, .txn = txn, - .timestamp = timestamp + .timestamp = timestamp, + .min_ttl = DEFAULT_MINTTL }; return map_walk(stash, &commit_rr, &baton); } diff --cc lib/layer/validate.c index 4c5d7e19a,000000000..8db3a581c mode 100644,000000..100644 --- a/lib/layer/validate.c +++ b/lib/layer/validate.c @@@ -1,754 -1,0 +1,754 @@@ +/* Copyright (C) 2014 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 +#include +#include +#include + +#include +#include +#include +#include + +#include "lib/dnssec.h" +#include "lib/layer/iterate.h" +#include "lib/resolve.h" +#include "lib/rplan.h" +#include "lib/defines.h" +#include "lib/nsrep.h" +#include "lib/module.h" + +#define DEBUG_MSG(fmt...) QRDEBUG(qry, "vldr", fmt) + +static int dname_parse(knot_dname_t **dname, const char *dname_str, mm_ctx_t *pool) +{ + if (!dname) { + return kr_error(EINVAL); + } + + knot_dname_t *owner = mm_alloc(pool, KNOT_DNAME_MAXLEN); + if (owner == NULL) { + return kr_error(ENOMEM); + } + knot_dname_t *aux = knot_dname_from_str(owner, dname_str, KNOT_DNAME_MAXLEN); + if (aux == NULL) { + mm_free(pool, owner); + return kr_error(ENOMEM); + } + + assert(!*dname); + *dname = owner; + return 0; +} + +static int uint_parse(const char *str, unsigned *u) +{ + char *err_pos; + long num = strtol(str, &err_pos, 10); + if ((*err_pos != '\0') || (num < 0)) { + return kr_error(EINVAL); + } + *u = (unsigned) num; + return 0; +} + +static int strcicmp(char const *a, char const *b) +{ + if (!a && !b) { + return 0; + } + if (!a) { + return -1; + } + if (!b) { + return 1; + } + for ( ; ; ++a, ++b) { + int d = tolower(*a) - tolower(*b); + if ((d != 0) || (*a == '\0')) { + return d; + } + } +} + +static int algorithm_parse(const char *str, unsigned *u) +{ + int ret = uint_parse(str, u); + if (ret == 0) { + return 0; + } + + const lookup_table_t *item = knot_dnssec_alg_names; + while (item->id) { + if (strcicmp(str, item->name) == 0) { + break; + } + ++item; + } + + if (!item->id) { + return kr_error(ENOENT); + } + + *u = (unsigned) item->id; + return 0; +} + +static int hex2value(const char hex) +{ + if ((hex >= '0') && (hex <= '9')) { + return hex - '0'; + } else if ((hex >= 'a') && (hex <= 'f')) { + return hex - 'a' + 10; + } else if ((hex >= 'A') && (hex <= 'F')) { + return hex - 'A' + 10; + } else { + return -1; + } +} + +static int hex2byte(const char hex[2], uint8_t *u) +{ + int d0, d1; + d0 = hex2value(hex[0]); + d1 = hex2value(hex[1]); + + if ((d0 == -1) || (d1 == -1)) { + return kr_error(EINVAL); + } + + *u = ((d0 & 0x0f) << 4) | (d1 & 0x0f); + return 0; +} + +static int ta_ds_parse(uint8_t *rd, size_t *rd_written, size_t rd_maxsize, const char *seps, char **saveptr) +{ + if (!rd || !rd_written || !seps || !saveptr) { + return kr_error(EINVAL); + } + + int ret = 0; + const char *token; + unsigned aux; + + /* Key tag. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = uint_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint16_t key_tag = aux; + + /* Algorithm. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = algorithm_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint8_t algorithm = aux; + + /* Digest type. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = uint_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint8_t digest_type = aux; + + size_t rd_pos = 0; + if (rd_maxsize >= 4) { + * (uint16_t *) (rd + rd_pos) = htons(key_tag); rd_pos += 2; + *(rd + rd_pos++) = algorithm; + *(rd + rd_pos++) = digest_type; + } else { + return kr_error(EINVAL); + } + + char hexbuf[2]; + int i = 0; + while ((token = strtok_r(NULL, seps, saveptr)) != NULL) { + for (int j = 0; j < strlen(token); ++j) { + hexbuf[i++] = token[j]; + if (i == 2) { + uint8_t byte; + ret = hex2byte(hexbuf, &byte); + if (ret != 0) { + return ret; + } + i = 0; + + if (rd_pos < rd_maxsize) { + *(rd + rd_pos++) = byte; + } else { + return kr_error(ENOMEM); + } + } + } + } + + if (i != 0) { + return kr_error(EINVAL); + } + + *rd_written = rd_pos; + return 0; +} + +static int base2bytes(const uint8_t base[4], uint8_t bytes[3], unsigned *valid) +{ + int32_t decoded = base64_decode(base, 4, bytes, 3); + if (decoded < 0) { + return kr_error(EINVAL); + } + *valid = decoded; + return 0; +} + +int ta_dnskey_parse(uint8_t *rd, size_t *rd_written, size_t rd_maxsize, const char *seps, char **saveptr) +{ + fprintf(stderr, "%s()\n", __func__); + + if (!rd || !rd_written || !seps || !saveptr) { + return kr_error(EINVAL); + } + + int ret = 0; + const char *token; + unsigned aux; + + /* Flags. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = uint_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint16_t flags = aux; + + /* Protocol. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = uint_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint8_t protocol = aux; + if (protocol != 3) { + return kr_error(EINVAL); + } + + /* Algorithm. */ + token = strtok_r(NULL, seps, saveptr); + if (!token) { + return kr_error(EINVAL); + } + ret = algorithm_parse(token, &aux); + if (ret != 0) { + return ret; + } + uint8_t algorithm = aux; + + size_t rd_pos = 0; + if (rd_maxsize >= 4) { + * (uint16_t *) (rd + rd_pos) = htons(flags); rd_pos += 2; + *(rd + rd_pos++) = protocol; + *(rd + rd_pos++) = algorithm; + } else { + return kr_error(EINVAL); + } + + uint8_t basebuf[4]; + uint8_t databuf[3]; + int i = 0; + while ((token = strtok_r(NULL, seps, saveptr)) != NULL) { + for (int j = 0; j < strlen(token); ++j) { + basebuf[i++] = token[j]; + if (i == 4) { + unsigned written; + ret = base2bytes(basebuf, databuf, &written); + if (ret != 0) { + return ret; + } + i = 0; + + if ((rd_pos + written) < rd_maxsize) { + memcpy(rd + rd_pos, databuf, written); + rd_pos += written; + } else { + return kr_error(ENOMEM); + } + } + } + } + + if (i != 0) { + return kr_error(EINVAL); + } + + *rd_written = rd_pos; + return 0; +} + +int kr_ta_parse(knot_rrset_t **rr, const char *ds_str, mm_ctx_t *pool) +{ +#define SEPARATORS " \t\n\r" +#define RDATA_MAXSIZE 640 + int ret = 0; + + if (!rr || !ds_str || !pool) { + ret = kr_error(EINVAL); + goto fail; + } + + char *ds_cpy = NULL; + knot_dname_t *owner = NULL; + knot_rdata_t *rdata = NULL; + knot_rrset_t *ds_set = NULL; + + size_t ds_len = strlen(ds_str) + 1; + ds_cpy = mm_alloc(pool, ds_len); + if (!ds_cpy) { + ret = kr_error(ENOMEM); + goto fail; + } + memcpy(ds_cpy, ds_str, ds_len); + char *saveptr = NULL, *token; + + /* Owner name. */ + token = strtok_r(ds_cpy, SEPARATORS, &saveptr); + if (!token) { + ret = kr_error(EINVAL); + goto fail; + } + ret = dname_parse(&owner, token, pool); + if (ret != 0) { + goto fail; + } + + /* Class. */ + uint16_t class; + token = strtok_r(NULL, SEPARATORS, &saveptr); + if (!token) { + ret = kr_error(EINVAL); + goto fail; + } + ret = knot_rrclass_from_string(token, &class); + if (ret != 0) { + ret = kr_error(EINVAL); + goto fail; + } + + /* Type. */ + uint16_t type; + token = strtok_r(NULL, SEPARATORS, &saveptr); + if (!token) { + ret = kr_error(EINVAL); + goto fail; + } + ret = knot_rrtype_from_string(token, &type); + if ((ret != 0) || + ((type != KNOT_RRTYPE_DS) && (type != KNOT_RRTYPE_DNSKEY))) { + ret = kr_error(EINVAL); + goto fail; + } + + /* Construct RDATA. */ + rdata = mm_alloc(pool, RDATA_MAXSIZE); + if (!rdata) { + ret = kr_error(ENOMEM); + goto fail; + } + size_t rd_written = 0; + + switch (type) { + case KNOT_RRTYPE_DS: + ret = ta_ds_parse(rdata, &rd_written, RDATA_MAXSIZE, SEPARATORS, &saveptr); + break; + case KNOT_RRTYPE_DNSKEY: + ret = ta_dnskey_parse(rdata, &rd_written, RDATA_MAXSIZE, SEPARATORS, &saveptr); + break; + default: + assert(0); + ret = kr_error(EINVAL); + break; + } + if (ret != 0) { + goto fail; + } + + ds_set = knot_rrset_new(owner, type, class, pool); + if (!ds_set) { + ret = kr_error(ENOMEM); + goto fail; + } + + ret = knot_rrset_add_rdata(ds_set, rdata, rd_written, 0, pool); + if (ret != 0) { + goto fail; + } + + *rr = ds_set; + ds_set = NULL; + +fail: + knot_rrset_free(&ds_set, pool); + mm_free(pool, rdata); + knot_dname_free(&owner, pool); + mm_free(pool, ds_cpy); + return ret; +#undef RDATA_MAXSIZE +#undef SEPARATORS +} + +/* Set resolution context and parameters. */ +static int begin(knot_layer_t *ctx, void *module_param) +{ + ctx->data = module_param; + return KNOT_STATE_PRODUCE; +} + +struct rrset_ids { + const knot_dname_t *owner; + uint16_t type; + uint32_t ttl; +}; + +/** Simplistic structure holding RR types that are contained in the packet. */ +struct contained_ids { + struct rrset_ids *ids; + size_t size; + size_t max; + mm_ctx_t *pool; +}; + +static int rrtypes_add(struct contained_ids *stored, const knot_rrset_t *rr) +{ + if (!stored || !rr) { + return kr_error(EINVAL); + } + + size_t i; + for (i = 0; i < stored->size; ++i) { + if ((knot_dname_cmp(stored->ids[i].owner, rr->owner) == 0) && + (stored->ids[i].type == rr->type)) { + break; + } + } + uint32_t rr_ttl = knot_rdata_ttl(knot_rdataset_at(&rr->rrs, 0)); + if (i < stored->size) { + if (stored->ids[i].ttl == rr_ttl) { + return kr_ok(); /* Type is stored. */ + } else { + /* RFC2181 5.2 */ + return kr_error(EINVAL); + } + } + + if (stored->max == stored->size) { +#define INCREMENT 8 + struct rrset_ids *new = mm_realloc(stored->pool, stored->ids, stored->max + INCREMENT * sizeof(*stored->ids), stored->max); + if (new) { + stored->ids = new; + stored->max += INCREMENT * sizeof(uint16_t); + } else { + return kr_error(ENOMEM); + } +#undef INCREMENT + } + assert(stored->max > stored->size); + + stored->ids[stored->size].owner = rr->owner; + stored->ids[stored->size].type = rr->type; + stored->ids[stored->size].ttl = rr_ttl; + ++stored->size; + return kr_ok(); +} + +static int validate_section(struct kr_query *qry, knot_pkt_t *answer, + knot_section_t section_id, mm_ctx_t *pool) +{ + const knot_pktsection_t *sec = knot_pkt_section(answer, section_id); + if (!sec) { + return kr_ok(); + } + + int ret = kr_ok(); + struct contained_ids stored = {0, }; + stored.pool = pool; + knot_rrset_t *covered = NULL; + + /* Determine RR types contained in the section. */ + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if (rr->type == KNOT_RRTYPE_RRSIG) { + continue; + } + if ((rr->type == KNOT_RRTYPE_NS) && (section_id == KNOT_AUTHORITY)) { + continue; + } + ret = rrtypes_add(&stored, rr); + if (ret != 0) { + goto fail; + } + } + + for (size_t i = 0; i < stored.size; ++i) { + knot_rrset_free(&covered, pool); + /* Construct a RRSet. */ + for (unsigned j = 0; j < sec->count; ++j) { + const knot_rrset_t *rr = knot_pkt_rr(sec, j); + if ((rr->type != stored.ids[i].type) || + (knot_dname_cmp(rr->owner, stored.ids[i].owner) != 0)) { + continue; + } + + if (covered) { + ret = knot_rdataset_merge(&covered->rrs, &rr->rrs, pool); + if (ret != 0) { + goto fail; + } + } else { + covered = knot_rrset_copy(rr, pool); + if (!covered) { + ret = kr_error(ENOMEM); + goto fail; + } + } + } + /* Validate RRSet. */ + /* Can't use qry->zone_cut.name directly, as this name can + * change when updating cut information before validation. + */ + const knot_dname_t *zone_name = qry->zone_cut.key ? qry->zone_cut.key->owner : NULL; + ret = kr_rrset_validate(sec, covered, qry->zone_cut.key, zone_name, qry->timestamp.tv_sec); + if (ret != 0) { + break; + } + } + +fail: + mm_free(stored.pool, stored.ids); + knot_rrset_free(&covered, pool); + return ret; +} + +static int validate_records(struct kr_query *qry, knot_pkt_t *answer, mm_ctx_t *pool) +{ +#warning TODO: validate RRSIGS (records with ZSK, keys with KSK), return FAIL if failed + if (!qry->zone_cut.key) { + DEBUG_MSG("<= no DNSKEY, can't validate\n"); + return kr_error(KNOT_DNSSEC_ENOKEY); + } + + int ret; + + ret = validate_section(qry, answer, KNOT_ANSWER, pool); + if (ret != 0) { + return ret; + } + ret = validate_section(qry, answer, KNOT_AUTHORITY, pool); + + return ret; +} + +static int validate_proof(struct kr_query *qry, knot_pkt_t *answer) +{ +#warning TODO: validate NSECx proof, RRSIGs will be checked later if it matches + return kr_ok(); +} + +static int validate_keyset(struct kr_query *qry, knot_pkt_t *answer) +{ + /* Merge DNSKEY records from answer */ + const knot_pktsection_t *an = knot_pkt_section(answer, KNOT_ANSWER); + for (unsigned i = 0; i < an->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(an, i); + if ((rr->type != KNOT_RRTYPE_DNSKEY) || + (knot_dname_cmp(rr->owner, qry->zone_cut.name) != 0)) { + continue; + } + /* Merge with zone cut. */ + if (!qry->zone_cut.key) { + qry->zone_cut.key = knot_rrset_copy(rr, qry->zone_cut.pool); + if (!qry->zone_cut.key) { + return kr_error(ENOMEM); + } + } else { + int ret = knot_rdataset_merge(&qry->zone_cut.key->rrs, + &rr->rrs, qry->zone_cut.pool); + if (ret != 0) { + knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); + return ret; + } + } + } + + if (!qry->zone_cut.key) { + /* TODO -- Not sure about the error value. */ + return kr_error(KNOT_DNSSEC_ENOKEY); + } + +#warning TODO: Ensure canonical format of the whole DNSKEY RRSet. (Also remove duplicities?) + + /* Check if there's a key for current TA. */ + int ret = kr_dnskeys_trusted(an, qry->zone_cut.key, qry->zone_cut.trust_anchor, qry->zone_cut.name, qry->timestamp.tv_sec); + if (ret != 0) { + knot_rrset_free(&qry->zone_cut.key, qry->zone_cut.pool); + return ret; + } + return kr_ok(); +} + +static int update_delegation(struct kr_query *qry, knot_pkt_t *answer) +{ + int ret = kr_ok(); + struct kr_zonecut *cut = &qry->zone_cut; + + DEBUG_MSG("<= referral, checking DS\n"); + + /* New trust anchor. */ + knot_rrset_t *new_ds = NULL; + const knot_pktsection_t *sec = knot_pkt_section(answer, KNOT_AUTHORITY); + for (unsigned i = 0; i < sec->count; ++i) { + const knot_rrset_t *rr = knot_pkt_rr(sec, i); + if ((rr->type != KNOT_RRTYPE_DS) || + (knot_dname_cmp(rr->owner, cut->name) != 0)) { + continue; + } + if (new_ds) { + ret = knot_rdataset_merge(&new_ds->rrs, &rr->rrs, cut->pool); + if (ret != 0) { + goto fail; + } + } else { + new_ds = knot_rrset_copy(rr, cut->pool); + if (!new_ds) { + ret = kr_error(ENOMEM); + goto fail; + } + } + } + + if (new_ds) { + knot_rrset_free(&cut->trust_anchor, cut->pool); + cut->trust_anchor = new_ds; + new_ds = NULL; + + /* It is very likely, that the keys don't match now. */ + knot_rrset_free(&cut->key, cut->pool); + } + +fail: + knot_rrset_free(&new_ds, cut->pool); + return ret; +} + +static int validate(knot_layer_t *ctx, knot_pkt_t *pkt) +{ + int ret; + struct kr_request *req = ctx->data; + struct kr_query *qry = kr_rplan_current(&req->rplan); + if (ctx->state & KNOT_STATE_FAIL) { + return ctx->state; + } + + /* Pass-through if user doesn't want secure answer. */ - if (!(req->flags & KR_REQ_DNSSEC)) { ++ if (!(req->options & QUERY_DNSSEC_WANT)) { + return ctx->state; + } + + /* Server didn't copy back DO=1, this is okay if it doesn't have DS => insecure. + * If it has DS, it must be secured, fail it as bogus. */ + if (!knot_pkt_has_dnssec(pkt)) { + DEBUG_MSG("<= asked with DO=1, got insecure response\n"); +#warning TODO: fail and retry if it has TA, otherwise flag as INSECURE and continue + return KNOT_STATE_FAIL; + } + + /* Validate non-existence proof if not positive answer. */ + if (knot_wire_get_rcode(pkt->wire) == KNOT_RCODE_NXDOMAIN) { + ret = validate_proof(qry, pkt); + if (ret != 0) { + DEBUG_MSG("<= bad NXDOMAIN proof\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + } + + /* Check if this is a DNSKEY answer, check trust chain and store. */ + uint16_t qtype = knot_pkt_qtype(pkt); + if (qtype == KNOT_RRTYPE_DNSKEY) { + ret = validate_keyset(qry, pkt); + if (ret != 0) { + DEBUG_MSG("<= bad keys, broken trust chain\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + } + + /* Validate all records, fail as bogus if it doesn't match. */ + ret = validate_records(qry, pkt, req->rplan.pool); + if (ret != 0) { + DEBUG_MSG("<= couldn't validate RRSIGs\n"); + qry->flags |= QUERY_DNSSEC_BOGUS; + return KNOT_STATE_FAIL; + } + + /* Update trust anchor. */ + if (qtype == KNOT_RRTYPE_NS) { + ret = update_delegation(qry, pkt); + if (ret != 0) { + return KNOT_STATE_FAIL; + } + + if (!qry->zone_cut.key) { + DEBUG_MSG("<= missing keys for new cut\n"); + struct kr_query *next = kr_rplan_push(&req->rplan, qry->parent, qry->zone_cut.name, qry->sclass, KNOT_RRTYPE_DNSKEY); + if (!next) { + return KNOT_STATE_FAIL; + } + } + } + + DEBUG_MSG("<= answer valid, OK\n"); + return ctx->state; +} + +/** Module implementation. */ +const knot_layer_api_t *validate_layer(struct kr_module *module) +{ + static const knot_layer_api_t _layer = { + .begin = &begin, + .consume = &validate, + }; + return &_layer; +} + +KR_MODULE_EXPORT(validate) diff --cc lib/resolve.c index 24a1d3e7e,75f0d7bb4..3c627f2a3 --- a/lib/resolve.c +++ b/lib/resolve.c @@@ -228,9 -239,9 +239,13 @@@ static int answer_prepare(knot_pkt_t *a if (knot_pkt_init_response(answer, query) != 0) { return kr_error(ENOMEM); /* Failed to initialize answer */ } ++ /* Set DNSSEC required flag if query contains DO=1 */ ++ if (knot_pkt_has_dnssec(query)) { ++ req->options |= QUERY_DNSSEC_WANT; ++ } /* Handle EDNS in the query */ - if (knot_pkt_has_edns(query) || (request->flags & KR_REQ_DNSSEC)) { - int ret = edns_create(request, answer); + if (knot_pkt_has_edns(query)) { + int ret = edns_create(answer, query, req); if (ret != 0){ return ret; } @@@ -245,10 -254,7 +260,10 @@@ static int answer_finalize(struct kr_re knot_pkt_begin(answer, KNOT_ADDITIONAL); if (answer->opt_rr) { return edns_put(answer); - + } + /* Set AD=1 if succeeded and requested secured answer. */ - if (state == KNOT_STATE_DONE && (request->flags & KR_REQ_DNSSEC)) { ++ if (state == KNOT_STATE_DONE && (request->options & QUERY_DNSSEC_WANT)) { + knot_wire_set_ad(answer->wire); } return kr_ok(); } @@@ -268,7 -279,7 +288,7 @@@ static int query_finalize(struct kr_req } int kr_resolve(struct kr_context* ctx, knot_pkt_t *answer, - const knot_dname_t *qname, uint16_t qclass, uint16_t qtype, unsigned flags) - const knot_dname_t *qname, uint16_t qclass, uint16_t qtype) ++ const knot_dname_t *qname, uint16_t qclass, uint16_t qtype, uint32_t options) { if (ctx == NULL || answer == NULL || qname == NULL) { return kr_error(EINVAL); @@@ -288,7 -299,6 +308,7 @@@ /* Initialize context. */ struct kr_request request; - request.flags = flags; ++ request.options = options; request.pool = pool; kr_resolve_begin(&request, ctx, answer); #ifndef NDEBUG @@@ -394,10 -408,9 +418,10 @@@ int kr_resolve_consume(struct kr_reques } /* Resolution failed, invalidate current NS. */ - if (state == KNOT_STATE_FAIL) { + if (request->state == KNOT_STATE_FAIL) { kr_nsrep_update_rtt(&qry->ns, KR_NS_TIMEOUT, ctx->cache_rtt); invalidate_ns(rplan, qry); + qry->flags &= ~QUERY_RESOLVED; /* Track RTT for iterative answers */ } else if (!(qry->flags & QUERY_CACHED)) { struct timeval now; @@@ -414,12 -427,7 +438,13 @@@ qry->flags &= ~(QUERY_CACHED|QUERY_TCP); } - knot_overlay_reset(&request->overlay); + ITERATE_LAYERS(request, knot_layer_reset); + + /* Do not finish with bogus answer. */ + if (qry->flags & QUERY_DNSSEC_BOGUS) { + return KNOT_STATE_FAIL; + } ++ return kr_rplan_empty(&request->rplan) ? KNOT_STATE_DONE : KNOT_STATE_PRODUCE; } @@@ -464,8 -473,7 +490,8 @@@ int kr_resolve_produce(struct kr_reques * now it's the time to look up closest zone cut from cache. */ if (qry->flags & QUERY_AWAIT_CUT) { - bool want_secured = (request->flags & KR_REQ_DNSSEC); - int ret = ns_fetch_cut(qry, request); ++ bool want_secured = (request->options & QUERY_DNSSEC_WANT); + int ret = ns_fetch_cut(qry, request, want_secured); if (ret != 0) { return KNOT_STATE_FAIL; } @@@ -521,15 -526,9 +553,10 @@@ ns_election struct sockaddr *addr = &qry->ns.addr.ip; inet_ntop(addr->sa_family, kr_nsrep_inaddr(qry->ns.addr), ns_str, sizeof(ns_str)); knot_dname_to_str(zonecut_str, qry->zone_cut.name, sizeof(zonecut_str)); - DEBUG_MSG("=> querying: '%s' score: %u zone cut: '%s' m12n: '%s'\n", ns_str, qry->ns.score, zonecut_str, qname_str); + knot_rrtype_to_string(knot_pkt_qtype(packet), type_str, sizeof(type_str)); + DEBUG_MSG("=> querying: '%s' score: %u zone cut: '%s' m12n: '%s' type: '%s'\n", ns_str, qry->ns.score, zonecut_str, qname_str, type_str); #endif - /* Prepare additional query */ - int ret = query_finalize(request, packet); - if (ret != 0) { - return KNOT_STATE_FAIL; - } gettimeofday(&qry->timestamp, NULL); *dst = &qry->ns.addr.ip; *type = (qry->flags & QUERY_TCP) ? SOCK_STREAM : SOCK_DGRAM; diff --cc lib/resolve.h index 7224bf481,c7e9e0b36..890f2e149 --- a/lib/resolve.h +++ b/lib/resolve.h @@@ -148,11 -142,10 +142,11 @@@ struct kr_request * @param qname resolved query name * @param qclass resolved query class * @param qtype resolved query type - * @param flags resolution flags ++ * @param options resolution options * @return 0 or an error code */ int kr_resolve(struct kr_context* ctx, knot_pkt_t *answer, - const knot_dname_t *qname, uint16_t qclass, uint16_t qtype, unsigned flags); - const knot_dname_t *qname, uint16_t qclass, uint16_t qtype); ++ const knot_dname_t *qname, uint16_t qclass, uint16_t qtype, uint32_t options); /** * Begin name resolution. diff --cc lib/rplan.h index 71ce52b3e,96785a6f7..214f8f1bf --- a/lib/rplan.h +++ b/lib/rplan.h @@@ -27,17 -27,17 +27,19 @@@ #include "lib/nsrep.h" #define QUERY_FLAGS(X) \ - X(NO_MINIMIZE , 1 << 0) /**< Don't minimize QNAME. */ \ - X(NO_THROTTLE , 1 << 1) /**< No query/slow NS throttling. */ \ - X(TCP , 1 << 2) /**< Use TCP for this query. */ \ - X(RESOLVED , 1 << 3) /**< Query is resolved. */ \ - X(AWAIT_IPV4 , 1 << 4) /**< Query is waiting for A address. */ \ - X(AWAIT_IPV6 , 1 << 5) /**< Query is waiting for AAAA address. */ \ - X(AWAIT_CUT , 1 << 6) /**< Query is waiting for zone cut lookup */ \ - X(SAFEMODE , 1 << 7) /**< Don't use fancy stuff (EDNS...) */ \ - X(CACHED , 1 << 8) /**< Query response is cached. */ \ - X(EXPIRING , 1 << 9) /**< Query response is cached, but expiring. */ \ - X(DNSSEC_BOGUS , 1 << 10) /**< Query response is DNSSEC bogus. */ \ + X(NO_MINIMIZE, 1 << 0) /**< Don't minimize QNAME. */ \ + X(NO_THROTTLE, 1 << 1) /**< No query/slow NS throttling. */ \ + X(TCP , 1 << 2) /**< Use TCP for this query. */ \ + X(RESOLVED , 1 << 3) /**< Query is resolved. */ \ + X(AWAIT_IPV4 , 1 << 4) /**< Query is waiting for A address. */ \ + X(AWAIT_IPV6 , 1 << 5) /**< Query is waiting for AAAA address. */ \ + X(AWAIT_CUT , 1 << 6) /**< Query is waiting for zone cut lookup */ \ + X(SAFEMODE , 1 << 7) /**< Don't use fancy stuff (EDNS...) */ \ + X(CACHED , 1 << 8) /**< Query response is cached. */ \ + X(EXPIRING , 1 << 9) /**< Query response is cached, but expiring. */ \ + X(NO_EXPIRING, 1 << 10) /**< Do not use expiring cached records. */ \ ++ X(DNSSEC_WANT , 1 << 11) /**< Want DNSSEC secured answer. */ \ ++ X(DNSSEC_BOGUS , 1 << 12) /**< Query response is DNSSEC bogus. */ /** Query flags */ enum kr_query_flag { diff --cc lib/zonecut.c index c6a5534dc,437192385..12ef20333 --- a/lib/zonecut.c +++ b/lib/zonecut.c @@@ -394,12 -327,8 +394,12 @@@ int kr_zonecut_find_cached(struct kr_co } /* Start at QNAME parent. */ - while (txn) { + while (txn && name) { - if (fetch_ns(ctx, cut, name, txn, timestamp) == 0) { + bool has_ta = !secured || fetch_ta(cut, name, txn, timestamp) == 0; + if (secured) { + fetch_dnskey(cut, name, txn, timestamp); + } + if (has_ta && fetch_ns(ctx, cut, name, txn, timestamp) == 0) { update_cut_name(cut, name); return kr_ok(); } diff --cc modules/stats/stats.c index 55f46689c,81263984c..5d36b26a3 --- a/modules/stats/stats.c +++ b/modules/stats/stats.c @@@ -99,9 -175,9 +175,9 @@@ static int collect(knot_layer_t *ctx } /* Query parameters and transport mode */ if (knot_pkt_has_edns(param->answer)) { - stat_add(map, "query.edns", 1); - if (param->flags & KR_REQ_DNSSEC) { - stat_add(map, "query.dnssec", 1); + stat_const_add(data, metric_query_edns, 1); - if (knot_pkt_has_dnssec(param->answer)) { ++ if (param->options & QUERY_DNSSEC_WANT) { + stat_const_add(data, metric_query_dnssec, 1); } } return ctx->state; diff --cc tests/test_integration.c index b853a1839,c43c34999..06d93a8dc --- a/tests/test_integration.c +++ b/tests/test_integration.c @@@ -136,17 -136,13 +136,17 @@@ static PyObject* resolve(PyObject *self knot_pkt_free(&query); return NULL; } - unsigned flags = 0; ++ uint32_t options = 0; + if (knot_pkt_has_dnssec(query)) { - flags = KR_REQ_DNSSEC; ++ options = QUERY_DNSSEC_WANT; + } /* Resolve query */ knot_pkt_t *answer = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, &global_mm); assert(answer); knot_pkt_init_response(answer, query); ret = kr_resolve(&global_context, answer, knot_pkt_qname(query), - knot_pkt_qclass(query), knot_pkt_qtype(query), flags); - knot_pkt_qclass(query), knot_pkt_qtype(query)); ++ knot_pkt_qclass(query), knot_pkt_qtype(query), options); /* Return wire and cleanup. */ PyObject *out = Py_BuildValue("s#", answer->wire, answer->size);