]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
lib/pktcache: packet cache (used for caching negative responses)
authorMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 3 May 2015 18:23:51 +0000 (20:23 +0200)
committerMarek Vavruša <marek.vavrusa@nic.cz>
Sun, 3 May 2015 18:23:51 +0000 (20:23 +0200)
the pktcache introduces two cache tags PKT/SEC for
basic/DNSSEC responses and stores negative answers in
the cache

daemon/engine.c
lib/cache.h
lib/layer/pktcache.c [new file with mode: 0644]
lib/lib.mk
lib/resolve.c

index d6b562780fe45a8c5de51b5ab8bb587b06cce6cd..32ba8d375a34c82964b48be9b24422e789f36d14 100644 (file)
@@ -119,6 +119,7 @@ static int init_resolver(struct engine *engine)
        /* Load basic modules */
        engine_register(engine, "iterate");
        engine_register(engine, "itercache");
+       engine_register(engine, "pktcache");
 
        /* Initialize storage backends */
        struct storage_api lmdb = {
index d5070dca42828fb992a9a9a7563f80a53a246ca0..83b7225a7e22aec7ad0e360240b8a68d5a4d9fe9 100644 (file)
 
 /** Cache entry tag */
 enum kr_cache_tag {
-       KR_CACHE_RR   = 0x01,
-       KR_CACHE_PKT  = 0x02,
-       KR_CACHE_USER = 0xF0
+       KR_CACHE_RR   = 'R',
+       KR_CACHE_PKT  = 'P',
+       KR_CACHE_SEC  = 'S',
+       KR_CACHE_USER = 0x80
 };
 
 /**
diff --git a/lib/layer/pktcache.c b/lib/layer/pktcache.c
new file mode 100644 (file)
index 0000000..12db00d
--- /dev/null
@@ -0,0 +1,162 @@
+/*  Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <libknot/descriptor.h>
+#include <libknot/rrset.h>
+#include <libknot/rrtype/soa.h>
+
+#include "lib/layer/iterate.h"
+#include "lib/cache.h"
+#include "lib/module.h"
+
+#define DEBUG_MSG(fmt...) QRDEBUG(kr_rplan_current(rplan), " pc ",  fmt)
+
+static inline uint8_t get_tag(knot_pkt_t *pkt)
+{
+       return knot_pkt_has_dnssec(pkt) ? KR_CACHE_SEC : KR_CACHE_PKT;
+}
+
+static int begin(knot_layer_t *ctx, void *module_param)
+{
+       ctx->data = module_param;
+       return ctx->state;
+}
+
+static int loot_cache(namedb_txn_t *txn, knot_pkt_t *pkt, uint8_t tag, uint32_t timestamp)
+{
+       const knot_dname_t *qname = knot_pkt_qname(pkt);
+       uint16_t rrtype = knot_pkt_qtype(pkt);
+       struct kr_cache_entry *entry;
+       entry = kr_cache_peek(txn, tag, qname, rrtype, &timestamp);
+       if (!entry) { /* Not in the cache */
+               return kr_error(ENOENT);
+       }
+
+       /* Copy answer, keep the original message id */
+       if (entry->count <= pkt->max_size) {
+               /* Keep original header and copy cached */
+               uint8_t header[KNOT_WIRE_HEADER_SIZE];
+               memcpy(header, pkt->wire, sizeof(header));
+               memcpy(pkt->wire, entry->data, entry->count);
+               pkt->size = entry->count;
+               pkt->parsed = 0;
+               pkt->reserved = 0;
+               /* Restore header bits */
+               knot_wire_set_id(pkt->wire, knot_wire_get_id(header));
+       }
+       return kr_ok();
+}
+
+static int peek(knot_layer_t *ctx, knot_pkt_t *pkt)
+{
+       struct kr_request *req = ctx->data;
+       struct kr_rplan *rplan = &req->rplan;
+       struct kr_query *qry = kr_rplan_current(rplan);
+       if (!qry || ctx->state & (KNOT_STATE_DONE|KNOT_STATE_FAIL)) {
+               return ctx->state;
+       }
+
+       /* Fetch packet from cache */
+       namedb_txn_t txn;
+       struct kr_cache *cache = req->ctx->cache;
+       if (kr_cache_txn_begin(cache, &txn, NAMEDB_RDONLY) != 0) {
+               return ctx->state;
+       }
+       uint32_t timestamp = qry->timestamp.tv_sec;
+       if (loot_cache(&txn, pkt, get_tag(req->answer), timestamp) != 0) {
+               kr_cache_txn_abort(&txn);
+               return ctx->state;
+       }
+
+       /* Mark as solved from cache */
+       DEBUG_MSG("=> satisfied from cache\n");
+       qry->flags |= QUERY_CACHED;
+       knot_wire_set_qr(pkt->wire);
+       kr_cache_txn_abort(&txn);
+       return KNOT_STATE_DONE;
+}
+
+static uint32_t packet_ttl(knot_pkt_t *pkt)
+{
+       uint32_t ttl = 0;
+       /* Fetch SOA from authority. */
+       const knot_pktsection_t *ns = knot_pkt_section(pkt, KNOT_AUTHORITY);
+       for (unsigned i = 0; i < ns->count; ++i) {
+               const knot_rrset_t *rr = knot_pkt_rr(ns, i);
+               if (rr->type == KNOT_RRTYPE_SOA) {
+                       ttl = knot_soa_minimum(&rr->rrs);
+                       break;
+               }
+       }
+       /* @todo Fetch TTL from NSEC* proof */
+       return ttl;
+}
+
+static int stash(knot_layer_t *ctx)
+{
+       struct kr_request *req = ctx->data;
+       struct kr_rplan *rplan = &req->rplan;
+       if (EMPTY_LIST(rplan->resolved) || ctx->state == KNOT_STATE_FAIL) {
+               return ctx->state; /* Don't cache anything if failed. */
+       }
+       knot_pkt_t *pkt = req->answer;
+       struct kr_query *qry = TAIL(rplan->resolved);
+       if (qry->flags & QUERY_CACHED || kr_response_classify(pkt) == PKT_NOERROR) {
+               return ctx->state; /* Cache only negative, not-cached answers. */
+       }
+       uint32_t ttl = packet_ttl(pkt);
+       if (ttl == 0) {
+               return ctx->state; /* No useable TTL, can't cache this. */
+       }
+
+       /* Open write transaction and prepare answer */
+       namedb_txn_t txn;
+       if (kr_cache_txn_begin(req->ctx->cache, &txn, 0) != 0) {
+               return ctx->state; /* Couldn't acquire cache, ignore. */
+       }
+       const knot_dname_t *qname = knot_pkt_qname(pkt);
+       uint16_t qtype = knot_pkt_qtype(pkt);
+       namedb_val_t data = { pkt->wire, pkt->size };
+       struct kr_cache_entry header = {
+               .timestamp = qry->timestamp.tv_sec,
+               .ttl = ttl,
+               .count = data.len
+       };
+
+       /* Stash answer in the cache */
+       int ret = kr_cache_insert(&txn, get_tag(pkt), qname, qtype, &header, data);     
+       if (ret == KNOT_ESPACE) {
+               kr_cache_txn_abort(&txn);
+       } else {
+               DEBUG_MSG("=> answer cached for TTL=%u\n", ttl);
+               kr_cache_txn_commit(&txn);
+       }
+       return ctx->state;
+}
+
+/** Module implementation. */
+const knot_layer_api_t *pktcache_layer(void)
+{
+       static const knot_layer_api_t _layer = {
+               .begin   = &begin,
+               .produce = &peek,
+               .finish  = &stash
+       };
+
+       return &_layer;
+}
+
+KR_MODULE_EXPORT(pktcache)
\ No newline at end of file
index 71dd361dee6771c12ac597c74ab0fa294d38848d..52b4881a0828692a83831585242f97f817f0e970 100644 (file)
@@ -7,6 +7,7 @@ libkresolve_SOURCES := \
        lib/generic/map.c      \
        lib/layer/iterate.c    \
        lib/layer/itercache.c  \
+       lib/layer/pktcache.c   \
        lib/utils.c            \
        lib/nsrep.c            \
        lib/module.c           \
index d3bb967f30792b7ff6972266052c4dcc3a5a67bb..6628400c4d426707df300b2b49a7de077b1c61b1 100644 (file)
@@ -418,6 +418,7 @@ int kr_resolve_finish(struct kr_request *request, int state)
                state = KNOT_STATE_FAIL;
        }
        /* Error during procesing, internal failure */
+       knot_overlay_finish(&request->overlay);
        if (state != KNOT_STATE_DONE) {
                knot_pkt_t *answer = request->answer;
                if (knot_wire_get_rcode(answer->wire) == KNOT_RCODE_NOERROR) {