]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
cache entry lists: move to a separate file
authorVladimír Čunát <vladimir.cunat@nic.cz>
Fri, 10 Nov 2017 14:05:16 +0000 (15:05 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Fri, 10 Nov 2017 16:55:10 +0000 (17:55 +0100)
lib/cache.c
lib/cache/entry_list.c [new file with mode: 0644]
lib/cache/impl.h
lib/lib.mk

index ec72e7cf47be0909bb6190bfc8d4216cffcb0703..64c89d1f149b71ba33cfd696bc5e20ae7c8109da 100644 (file)
 #include "lib/defines.h"
 #include "lib/utils.h"
 
+#include "lib/dnssec/nsec.h"
+#include "lib/dnssec/ta.h"
+#include "lib/layer/iterate.h"
+#include "lib/resolve.h"
+#include "lib/rplan.h"
+
+#include "lib/cache/impl.h"
+
 
 /** Cache version */
 static const uint16_t CACHE_VERSION = 1;
@@ -40,9 +48,6 @@ static const uint16_t CACHE_VERSION = 1;
 #define KEY_HSIZE (sizeof(uint8_t) + sizeof(uint16_t))
 #define KEY_SIZE (KEY_HSIZE + KNOT_DNAME_MAXLEN)
 
-/* Shorthand for operations on cache backend */
-#define cache_isvalid(cache) ((cache) && (cache)->api && (cache)->db)
-#define cache_op(cache, op, ...) (cache)->api->op((cache)->db, ## __VA_ARGS__)
 
 /** @internal Removes all records from cache. */
 static inline int cache_clear(struct kr_cache *cache)
@@ -113,6 +118,9 @@ int kr_cache_open(struct kr_cache *cache, const struct kr_cdb_api *api, struct k
        return 0;
 }
 
+
+#define cache_isvalid(cache) ((cache) && (cache)->api && (cache)->db)
+
 void kr_cache_close(struct kr_cache *cache)
 {
        if (cache_isvalid(cache)) {
@@ -145,13 +153,6 @@ int kr_cache_clear(struct kr_cache *cache)
 }
 
 
-#include "lib/dnssec/nsec.h"
-#include "lib/dnssec/ta.h"
-#include "lib/layer/iterate.h"
-#include "lib/resolve.h"
-#include "lib/rplan.h"
-
-#include "lib/cache/impl.h"
 
 
 
@@ -164,9 +165,7 @@ struct nsec_p {
        uint8_t *salt;
 };
 
-/** Check basic consistency of entry_h, not looking into ->data.
- * \note only exact hits and NSEC1 are really considered ATM. */
-static struct entry_h * entry_h_consistent(knot_db_val_t data, uint16_t ktype)
+struct entry_h * entry_h_consistent(knot_db_val_t data, uint16_t ktype)
 {
        /* Length checks. */
        if (data.len < offsetof(struct entry_h, data))
@@ -205,7 +204,7 @@ static struct entry_h * entry_h_consistent(knot_db_val_t data, uint16_t ktype)
 
 
 
-static int32_t get_new_ttl(const struct entry_h *entry, uint32_t current_time)
+int32_t get_new_ttl(const struct entry_h *entry, uint32_t current_time)
 {
        int32_t diff = current_time - entry->time;
        if (diff < 0) {
@@ -255,7 +254,7 @@ enum {
 
 
 
-/* TODO: move rdataset_* and pkt_* functions into a separate c-file. */
+/* TODO: move rdataset_* and pkt_* and entry2answer functions into a separate c-file. */
 /** Materialize a knot_rdataset_t from cache with given TTL.
  * Return the number of bytes consumed or an error code.
  */
@@ -390,103 +389,6 @@ static int rdataset_dematerialize(const knot_rdataset_t *rds, void * restrict da
        return kr_ok();
 }
 
-/** Given a valid entry header, find its length (i.e. offset of the next entry).
- * \param val The beginning of the data and the bound (read only).
- */
-static int entry_h_len(const knot_db_val_t val)
-{
-       const bool ok = val.data && ((ssize_t)val.len) > 0;
-       if (!ok) return kr_error(EINVAL);
-       const struct entry_h *eh = val.data;
-       const void *d = eh->data; /* iterates over the data in entry */
-       const void *data_bound = val.data + val.len;
-       if (d >= data_bound) return kr_error(EILSEQ);
-       if (!eh->is_packet) { /* Positive RRset + its RRsig set (may be empty). */
-               int sets = 2;
-               while (sets-- > 0) {
-                       if (d + 1 > data_bound) return kr_error(EILSEQ);
-                       uint8_t rr_count;
-                       memcpy(&rr_count, d++, sizeof(rr_count));
-                       for (int i = 0; i < rr_count; ++i) {
-                               if (d + 2 > data_bound) return kr_error(EILSEQ);
-                               uint16_t len;
-                               memcpy(&len, d, sizeof(len));
-                               d += 2 + len;
-                       }
-               }
-       } else { /* A "packet" (opaque ATM). */
-               if (d + 2 > data_bound) return kr_error(EILSEQ);
-               uint16_t len;
-               memcpy(&len, d, sizeof(len));
-               d += 2 + len;
-       }
-       if (d > data_bound) return kr_error(EILSEQ);
-       return d - val.data;
-}
-
-/** There may be multiple entries within, so rewind `val` to the one we want.
- *
- * ATM there are multiple types only for the NS ktype.
- * \return error code
- */
-static int entry_h_seek(knot_db_val_t *val, uint16_t type)
-{
-       uint16_t ktype;
-       switch (type) {
-       case KNOT_RRTYPE_NS:
-       case KNOT_RRTYPE_CNAME:
-       case KNOT_RRTYPE_DNAME:
-               ktype = KNOT_RRTYPE_NS;
-       default:
-               ktype = type;
-       }
-       if (ktype != KNOT_RRTYPE_NS) {
-               return kr_ok();
-       }
-       const struct entry_h *eh = entry_h_consistent(*val, ktype);
-       if (!eh) {
-               return kr_error(EILSEQ);
-       }
-
-       bool present;
-       switch (type) {
-       case KNOT_RRTYPE_NS:
-               present = eh->has_ns;
-               break;
-       case KNOT_RRTYPE_CNAME:
-               present = eh->has_cname;
-               break;
-       case KNOT_RRTYPE_DNAME:
-               present = eh->has_dname;
-               break;
-       default:
-               return kr_error(EINVAL);
-       }
-       if (!present) {
-               return kr_error(ENOENT);
-       }
-       /* count how many entries to skip */
-       int to_skip = 0;
-       switch (type) {
-       case KNOT_RRTYPE_DNAME:
-               to_skip += eh->has_cname;
-       case KNOT_RRTYPE_CNAME:
-               to_skip += eh->has_ns;
-       case KNOT_RRTYPE_NS:
-               break;
-       }
-       /* advance `val` and `eh` */
-       while (to_skip-- > 0) {
-               int len = entry_h_len(*val);
-               if (len < 0 || len > val->len) {
-                       return kr_error(len < 0 ? len : EILSEQ);
-                       // LATER: recovery, perhaps via removing the entry?
-               }
-               val->data += len;
-               val->len -= len;
-       }
-       return kr_ok();
-}
 
 /**
  */
@@ -1234,122 +1136,6 @@ do_soa:
 }
 
 
-/* See the header file. */
-int entry_h_splice(
-       knot_db_val_t *val_new_entry, uint8_t rank,
-       const knot_db_val_t key, const uint16_t ktype, const uint16_t type,
-       const knot_dname_t *owner/*log only*/,
-       const struct kr_query *qry, struct kr_cache *cache)
-{
-       /* Find the whole entry-set and the particular entry within. */
-       knot_db_val_t val_orig_all = { }, val_orig_entry = { };
-       const struct entry_h *eh_orig = NULL;
-       if (!kr_rank_test(rank, KR_RANK_SECURE) || ktype == KNOT_RRTYPE_NS) {
-               int ret = cache_op(cache, read, &key, &val_orig_all, 1);
-               if (ret) val_orig_all = (knot_db_val_t){ };
-               val_orig_entry = val_orig_all;
-               switch (entry_h_seek(&val_orig_entry, type)) {
-               case 0:
-                       ret = entry_h_len(val_orig_entry);
-                       if (ret >= 0) {
-                               val_orig_entry.len = ret;
-                               eh_orig = entry_h_consistent(val_orig_entry, type);
-                               if (eh_orig) {
-                                       break;
-                               }
-                       } /* otherwise fall through */
-               default:
-                       val_orig_entry = val_orig_all = (knot_db_val_t){};
-               case -ENOENT:
-                       val_orig_entry.len = 0;
-                       break;
-               };
-       }
-
-       if (!kr_rank_test(rank, KR_RANK_SECURE) && eh_orig) {
-               /* If equal rank was accepted, spoofing a *single* answer would be
-                * enough to e.g. override NS record in AUTHORITY section.
-                * This way they would have to hit the first answer
-                * (whenever TTL nears expiration). */
-               int32_t old_ttl = get_new_ttl(eh_orig, qry->creation_time.tv_sec);
-               if (old_ttl > 0 && !is_expiring(old_ttl, eh_orig->ttl)
-                   && rank <= eh_orig->rank) {
-                       WITH_VERBOSE {
-                               VERBOSE_MSG(qry, "=> not overwriting ");
-                               kr_rrtype_print(type, "", " ");
-                               kr_dname_print(owner, "", "\n");
-                       }
-                       return kr_error(EEXIST);
-               }
-       }
-
-       /* Obtain new storage from LMDB.
-        * Note: this does NOT invalidate val_orig_all.data. */
-       ssize_t storage_size = val_orig_all.len - val_orig_entry.len
-                               + val_new_entry->len;
-       assert(storage_size > 0);
-       knot_db_val_t val = { .len = storage_size, .data = NULL };
-       int ret = cache_op(cache, write, &key, &val, 1);
-       if (ret || !val.data || !val.len) {
-               assert(ret); /* otherwise "succeeding" but `val` is bad */
-               VERBOSE_MSG(qry, "=> failed LMDB write, ret = %d\n", ret);
-               return kr_error(ret ? ret : ENOSPC);
-       }
-
-       /* Write original data before entry, if any. */
-       const ssize_t len_before = val_orig_entry.data - val_orig_all.data;
-       assert(len_before >= 0);
-       if (len_before) {
-               memcpy(val.data, val_orig_all.data, len_before);
-       }
-       /* Write original data after entry, if any. */
-       const ssize_t len_after = val_orig_all.len - val_orig_entry.len;
-       assert(len_after >= 0);
-       if (len_after) {
-               memcpy(val.data + len_before + val_new_entry->len,
-                       val_orig_entry.data + val_orig_entry.len, len_after);
-       }
-
-       val_new_entry->data = val.data + len_before;
-       {
-               struct entry_h *eh = val_new_entry->data;
-               memset(eh, 0, offsetof(struct entry_h, data));
-               /* In case (len_before == 0 && ktype == KNOT_RRTYPE_NS) the *eh
-                * set below would be uninitialized and the caller wouldn't be able
-                * to do it after return, as that would overwrite what we do below. */
-       }
-       /* The multi-entry type needs adjusting the flags. */
-       if (ktype == KNOT_RRTYPE_NS) {
-               struct entry_h *eh = val.data;
-               if (!len_before) {
-               }
-               if (val_orig_all.len) {
-                       const struct entry_h *eh0 = val_orig_all.data;
-                       /* ENTRY_H_FLAGS */
-                       eh->nsec1_pos = eh0->nsec1_pos;
-                       eh->nsec3_cnt = eh0->nsec3_cnt;
-                       eh->has_ns    = eh0->has_ns;
-                       eh->has_cname = eh0->has_cname;
-                       eh->has_dname = eh0->has_dname;
-               }
-               /* we just added/replaced some type */
-               switch (type) {
-               case KNOT_RRTYPE_NS:
-                       eh->has_ns = true;  break;
-               case KNOT_RRTYPE_CNAME:
-                       eh->has_cname = true;  break;
-               case KNOT_RRTYPE_DNAME:
-                       eh->has_dname = true;  break;
-               default:
-                       assert(false);
-               }
-       }
-       return kr_ok();
-}
-
-
-
-
 
 static int stash_rrset(const ranked_rr_array_t *arr, int arr_i, uint32_t min_ttl,
                        const struct kr_query *qry, struct kr_cache *cache);
diff --git a/lib/cache/entry_list.c b/lib/cache/entry_list.c
new file mode 100644 (file)
index 0000000..a6861f2
--- /dev/null
@@ -0,0 +1,231 @@
+/*  Copyright (C) 2017 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 <https://www.gnu.org/licenses/>.
+ */
+
+/** @file
+ * Implementation of chaining in struct entry_h.  Prototypes in ./impl.h
+ */
+
+#include "lib/cache/impl.h"
+
+
+/** Given a valid entry header, find its length (i.e. offset of the next entry).
+ * \param val The beginning of the data and the bound (read only).
+ */
+static int entry_h_len(const knot_db_val_t val)
+{
+       const bool ok = val.data && ((ssize_t)val.len) > 0;
+       if (!ok) return kr_error(EINVAL);
+       const struct entry_h *eh = val.data;
+       const void *d = eh->data; /* iterates over the data in entry */
+       const void *data_bound = val.data + val.len;
+       if (d >= data_bound) return kr_error(EILSEQ);
+       if (!eh->is_packet) { /* Positive RRset + its RRsig set (may be empty). */
+               int sets = 2;
+               while (sets-- > 0) {
+                       if (d + 1 > data_bound) return kr_error(EILSEQ);
+                       uint8_t rr_count;
+                       memcpy(&rr_count, d++, sizeof(rr_count));
+                       for (int i = 0; i < rr_count; ++i) {
+                               if (d + 2 > data_bound) return kr_error(EILSEQ);
+                               uint16_t len;
+                               memcpy(&len, d, sizeof(len));
+                               d += 2 + len;
+                       }
+               }
+       } else { /* A "packet" (opaque ATM). */
+               if (d + 2 > data_bound) return kr_error(EILSEQ);
+               uint16_t len;
+               memcpy(&len, d, sizeof(len));
+               d += 2 + len;
+       }
+       if (d > data_bound) return kr_error(EILSEQ);
+       return d - val.data;
+}
+
+/* See the header file. */
+int entry_h_seek(knot_db_val_t *val, uint16_t type)
+{
+       uint16_t ktype;
+       switch (type) {
+       case KNOT_RRTYPE_NS:
+       case KNOT_RRTYPE_CNAME:
+       case KNOT_RRTYPE_DNAME:
+               ktype = KNOT_RRTYPE_NS;
+       default:
+               ktype = type;
+       }
+       if (ktype != KNOT_RRTYPE_NS) {
+               return kr_ok();
+       }
+       const struct entry_h *eh = entry_h_consistent(*val, ktype);
+       if (!eh) {
+               return kr_error(EILSEQ);
+       }
+
+       bool present;
+       switch (type) {
+       case KNOT_RRTYPE_NS:
+               present = eh->has_ns;
+               break;
+       case KNOT_RRTYPE_CNAME:
+               present = eh->has_cname;
+               break;
+       case KNOT_RRTYPE_DNAME:
+               present = eh->has_dname;
+               break;
+       default:
+               return kr_error(EINVAL);
+       }
+       if (!present) {
+               return kr_error(ENOENT);
+       }
+       /* count how many entries to skip */
+       int to_skip = 0;
+       switch (type) {
+       case KNOT_RRTYPE_DNAME:
+               to_skip += eh->has_cname;
+       case KNOT_RRTYPE_CNAME:
+               to_skip += eh->has_ns;
+       case KNOT_RRTYPE_NS:
+               break;
+       }
+       /* advance `val` and `eh` */
+       while (to_skip-- > 0) {
+               int len = entry_h_len(*val);
+               if (len < 0 || len > val->len) {
+                       return kr_error(len < 0 ? len : EILSEQ);
+                       // LATER: recovery, perhaps via removing the entry?
+               }
+               val->data += len;
+               val->len -= len;
+       }
+       return kr_ok();
+}
+
+
+/* See the header file. */
+int entry_h_splice(
+       knot_db_val_t *val_new_entry, uint8_t rank,
+       const knot_db_val_t key, const uint16_t ktype, const uint16_t type,
+       const knot_dname_t *owner/*log only*/,
+       const struct kr_query *qry, struct kr_cache *cache)
+{
+       /* Find the whole entry-set and the particular entry within. */
+       knot_db_val_t val_orig_all = { }, val_orig_entry = { };
+       const struct entry_h *eh_orig = NULL;
+       if (!kr_rank_test(rank, KR_RANK_SECURE) || ktype == KNOT_RRTYPE_NS) {
+               int ret = cache_op(cache, read, &key, &val_orig_all, 1);
+               if (ret) val_orig_all = (knot_db_val_t){ };
+               val_orig_entry = val_orig_all;
+               switch (entry_h_seek(&val_orig_entry, type)) {
+               case 0:
+                       ret = entry_h_len(val_orig_entry);
+                       if (ret >= 0) {
+                               val_orig_entry.len = ret;
+                               eh_orig = entry_h_consistent(val_orig_entry, type);
+                               if (eh_orig) {
+                                       break;
+                               }
+                       } /* otherwise fall through */
+               default:
+                       val_orig_entry = val_orig_all = (knot_db_val_t){};
+               case -ENOENT:
+                       val_orig_entry.len = 0;
+                       break;
+               };
+       }
+
+       if (!kr_rank_test(rank, KR_RANK_SECURE) && eh_orig) {
+               /* If equal rank was accepted, spoofing a *single* answer would be
+                * enough to e.g. override NS record in AUTHORITY section.
+                * This way they would have to hit the first answer
+                * (whenever TTL nears expiration). */
+               int32_t old_ttl = get_new_ttl(eh_orig, qry->creation_time.tv_sec);
+               if (old_ttl > 0 && !is_expiring(old_ttl, eh_orig->ttl)
+                   && rank <= eh_orig->rank) {
+                       WITH_VERBOSE {
+                               VERBOSE_MSG(qry, "=> not overwriting ");
+                               kr_rrtype_print(type, "", " ");
+                               kr_dname_print(owner, "", "\n");
+                       }
+                       return kr_error(EEXIST);
+               }
+       }
+
+       /* Obtain new storage from LMDB.
+        * Note: this does NOT invalidate val_orig_all.data. */
+       ssize_t storage_size = val_orig_all.len - val_orig_entry.len
+                               + val_new_entry->len;
+       assert(storage_size > 0);
+       knot_db_val_t val = { .len = storage_size, .data = NULL };
+       int ret = cache_op(cache, write, &key, &val, 1);
+       if (ret || !val.data || !val.len) {
+               assert(ret); /* otherwise "succeeding" but `val` is bad */
+               VERBOSE_MSG(qry, "=> failed LMDB write, ret = %d\n", ret);
+               return kr_error(ret ? ret : ENOSPC);
+       }
+
+       /* Write original data before entry, if any. */
+       const ssize_t len_before = val_orig_entry.data - val_orig_all.data;
+       assert(len_before >= 0);
+       if (len_before) {
+               memcpy(val.data, val_orig_all.data, len_before);
+       }
+       /* Write original data after entry, if any. */
+       const ssize_t len_after = val_orig_all.len - val_orig_entry.len;
+       assert(len_after >= 0);
+       if (len_after) {
+               memcpy(val.data + len_before + val_new_entry->len,
+                       val_orig_entry.data + val_orig_entry.len, len_after);
+       }
+
+       val_new_entry->data = val.data + len_before;
+       {
+               struct entry_h *eh = val_new_entry->data;
+               memset(eh, 0, offsetof(struct entry_h, data));
+               /* In case (len_before == 0 && ktype == KNOT_RRTYPE_NS) the *eh
+                * set below would be uninitialized and the caller wouldn't be able
+                * to do it after return, as that would overwrite what we do below. */
+       }
+       /* The multi-entry type needs adjusting the flags. */
+       if (ktype == KNOT_RRTYPE_NS) {
+               struct entry_h *eh = val.data;
+               if (!len_before) {
+               }
+               if (val_orig_all.len) {
+                       const struct entry_h *eh0 = val_orig_all.data;
+                       /* ENTRY_H_FLAGS */
+                       eh->nsec1_pos = eh0->nsec1_pos;
+                       eh->nsec3_cnt = eh0->nsec3_cnt;
+                       eh->has_ns    = eh0->has_ns;
+                       eh->has_cname = eh0->has_cname;
+                       eh->has_dname = eh0->has_dname;
+               }
+               /* we just added/replaced some type */
+               switch (type) {
+               case KNOT_RRTYPE_NS:
+                       eh->has_ns = true;  break;
+               case KNOT_RRTYPE_CNAME:
+                       eh->has_cname = true;  break;
+               case KNOT_RRTYPE_DNAME:
+                       eh->has_dname = true;  break;
+               default:
+                       assert(false);
+               }
+       }
+       return kr_ok();
+}
+
index 760b4aa982181f2183df2189365653e45368b510..ca07428f9b46c8c904d17410197723c775769464 100644 (file)
@@ -18,6 +18,7 @@
  * Header internal for cache implementation(s).
  * Only LMDB works for now.
  */
+#pragma once
 
 #include <stdbool.h>
 #include <stdint.h>
@@ -26,6 +27,7 @@
 #include <libknot/db/db.h>
 #include <libknot/dname.h>
 
+#include "lib/cdb.h"
 #include "lib/resolve.h"
 
 /** Cache entry header
@@ -54,6 +56,13 @@ struct entry_h {
 };
 
 
+/** Check basic consistency of entry_h, not looking into ->data.
+ * (for is_packet the length of data is checked)
+ * \note only exact hits and NSEC1 are really considered ATM.
+ */
+struct entry_h * entry_h_consistent(knot_db_val_t data, uint16_t ktype);
+
+
 // TODO
 #define KR_CACHE_KEY_MAXLEN (KNOT_DNAME_MAXLEN + 100)
 
@@ -75,6 +84,14 @@ static inline size_t key_nwz_off(const struct key *k)
 knot_db_val_t key_exact_type_maypkt(struct key *k, uint16_t type);
 
 
+/* entry_h chaining; implementation in ./entry_list.c */
+
+/** There may be multiple entries within, so rewind `val` to the one we want.
+ *
+ * ATM there are multiple types only for the NS ktype.
+ * \return error code
+ */
+int entry_h_seek(knot_db_val_t *val, uint16_t type);
 
 /** Prepare space to insert an entry.
  *
@@ -93,7 +110,6 @@ int entry_h_splice(
        const struct kr_query *qry, struct kr_cache *cache);
 
 
-
 /* Packet caching; implementation in ./entry_pkt.c */
 
 /** Stash the packet into cache (if suitable, etc.) */
@@ -119,6 +135,12 @@ static inline bool is_expiring(uint32_t orig_ttl, uint32_t new_ttl)
        return 100 * (nttl - 5) < orig_ttl;
 }
 
+int32_t get_new_ttl(const struct entry_h *entry, uint32_t current_time);
 
 #define VERBOSE_MSG(qry, fmt...) QRVERBOSE((qry), "cach",  fmt)
 
+
+
+/** Shorthand for operations on cache backend */
+#define cache_op(cache, op, ...) (cache)->api->op((cache)->db, ## __VA_ARGS__)
+
index 06a0684eb6816bdb625ce5c99e925243ded57a99..f5d6c55dd423a48182683a6441b5706385640f8b 100644 (file)
@@ -16,6 +16,7 @@ libkres_SOURCES := \
        lib/zonecut.c          \
        lib/rplan.c            \
        lib/cache.c            \
+       lib/cache/entry_list.c \
        lib/cache/entry_pkt.c  \
        lib/cdb_lmdb.c