]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
Merge branch 'master' into cache-rrsig-wip
authorMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 23 Jul 2015 14:54:41 +0000 (16:54 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Thu, 23 Jul 2015 14:54:41 +0000 (16:54 +0200)
Conflicts:
lib/layer/rrcache.c
lib/resolve.c
lib/resolve.h
lib/rplan.h
lib/zonecut.c
modules/stats/stats.c

1  2 
daemon/engine.c
daemon/main.c
lib/layer/pktcache.c
lib/layer/rrcache.c
lib/layer/validate.c
lib/resolve.c
lib/resolve.h
lib/rplan.h
lib/zonecut.c
modules/stats/stats.c
tests/test_integration.c

diff --cc daemon/engine.c
Simple merge
diff --cc daemon/main.c
Simple merge
index af91ef84a6da4f4cb8c024ca5fb7a164b53cc244,eb62571cb84f0899956cbbaff83d2bc880d8b1d2..dcd8ce920309bd4af82244dadef2bf712ed67d18
@@@ -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)
index 6ef46da5f200e1944136a84530a692dd9d6d5462,9d017caf64d2a1d17c44e3acc921406e253d7272..6710c0ea6d7c0546e7d1caa14aad08645a421f36
  #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;
  /** @internal Baton for stash_commit */
  struct stash_baton
  {
 +      struct kr_request *req;
        struct kr_cache_txn *txn;
        unsigned timestamp;
+       uint32_t min_ttl;
  };
  
-       if (!(baton->req->flags & KR_REQ_DNSSEC)) {
 +static int commit_rrsig(struct stash_baton *baton, knot_rrset_t *rr)
 +{
 +      /* If not doing secure resolution, ignore (unvalidated) RRSIGs. */
++      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.
        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);
  }
index 4c5d7e19a904471525ad383551df1a8e8bea9966,0000000000000000000000000000000000000000..8db3a581c5293449db8f38475a417f7639c65a1e
mode 100644,000000..100644
--- /dev/null
@@@ -1,754 -1,0 +1,754 @@@
-       if (!(req->flags & KR_REQ_DNSSEC)) {
 +/*  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 <assert.h>
 +#include <ctype.h>
 +#include <sys/time.h>
 +#include <string.h>
 +
 +#include <libknot/descriptor.h>
 +#include <libknot/internal/base64.h>
 +#include <libknot/rrtype/rdname.h>
 +#include <libknot/rrtype/dnskey.h>
 +
 +#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->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 24a1d3e7e90170640275782cc701e4d6a92d81d1,75f0d7bb40bf5f5767dbd05bf409ec5d49e8d5c8..3c627f2a396d530856895ddac625472eca65ff4d
@@@ -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);
  
        /* 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;
                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 7224bf481ef406ea68a68d6335a8ac46df8b5fba,c7e9e0b36c0003ee9ded009cfb73192116b8c1ae..890f2e1496b9663718d88e3033cfb69fcba2f2e7
@@@ -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 71ce52b3ee894ad1e2c62fe18a2d1c5c2f4dc903,96785a6f78f4409ec2aa517df6e2bfa2bfc05ad6..214f8f1bf4e48c032a084bd3d4dec86abdb1913d
  #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 c6a5534dc97dc32c0705e16f35539d8839ebe89d,437192385dfe0b013b8291b4a7400cb2274755a8..12ef203335215128c48da48d7b9a88fbab49302f
@@@ -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();
                }
index 55f46689cedf64163db2e9a55b9ffc9b5a055c66,81263984c970c33976e2a127ad1eeebf743beaa5..5d36b26a31e82855e5666c8c9197c074c1758499
@@@ -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;
index b853a18395bfddb14cc36a1323fbc00adccb50b5,c43c34999331dd84fff7bce92812c770b9154499..06d93a8dcef4a151ef273d616a70f3e318b5170a
@@@ -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);