]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon: root zone import
authorGrigorii Demidov <grigorii.demidov@nic.cz>
Tue, 27 Feb 2018 16:25:01 +0000 (17:25 +0100)
committerPetr Špaček <petr.spacek@nic.cz>
Wed, 18 Apr 2018 15:08:15 +0000 (17:08 +0200)
daemon/bindings.c
daemon/daemon.mk
daemon/worker.c
daemon/worker.h
daemon/zimport.c [new file with mode: 0644]
daemon/zimport.h [new file with mode: 0644]
lib/generic/array.h
lib/utils.c
lib/utils.h

index b9045b358ac4e2f81d9c88dd0b44092253f39377..23ac4f7a4e7041d73aec0c6f73e37943ca54a988 100644 (file)
@@ -26,6 +26,7 @@
 #include "daemon/bindings.h"
 #include "daemon/worker.h"
 #include "daemon/tls.h"
+#include "daemon/zimport.h"
 
 #define xstr(s) str(s)
 #define str(s) #s
@@ -1149,6 +1150,73 @@ static int cache_ns_tout(lua_State *L)
        return 1;
 }
 
+/** Zone import completion callback.
+ * Deallocates zone import context. */
+static void cache_zone_import_cb(int state, void *param)
+{
+       assert (param);
+       (void)state;
+       struct worker_ctx *worker = (struct worker_ctx *)param;
+       assert (worker->z_import);
+       zi_free(worker->z_import);
+       worker->z_import = NULL;
+}
+
+/** Import zone from file. */
+static int cache_zone_import(lua_State *L)
+{
+       struct worker_ctx *worker = wrk_luaget(L);
+       if (!worker) {
+               return 0;
+       }
+
+       if (worker->z_import && zi_import_started(worker->z_import)) {
+               format_error(L, "import has already started");
+               lua_error(L);
+       }
+
+       struct engine *engine = engine_luaget(L);
+       if (!engine) {
+               return 0;
+       }
+       struct kr_cache *cache = &engine->resolver.cache;
+       if (!kr_cache_is_open(cache)) {
+               return 0;
+       }
+
+       /* Check parameters */
+       int n = lua_gettop(L);
+       if (n < 1 || !lua_isstring(L, 1)) {
+               format_error(L, "expected 'cache.zone_import(string key)'");
+               lua_error(L);
+       }
+
+       /* Parse zone file */
+       const char *zone_file = lua_tostring(L, 1);
+
+       const char *default_origin = NULL; /* TODO */
+       uint16_t default_rclass = 1;
+       uint32_t default_ttl = 0;
+
+       if (worker->z_import == NULL) {
+               worker->z_import = zi_allocate(worker, cache_zone_import_cb, worker);
+               if (worker->z_import == NULL) {
+                       format_error(L, "can't allocate zone import context");
+                       lua_error(L);
+               }
+       }
+
+       int ret = zi_zone_import(worker->z_import, zone_file, default_origin,
+                                default_rclass, default_ttl);
+
+       if (ret != 0) {
+               format_error(L, "error parsing zone file");
+               lua_error(L);
+       }
+
+       lua_pushstring(L, "zone file successfully parsed, import started");
+       return 1;
+}
 
 int lib_cache(lua_State *L)
 {
@@ -1165,6 +1233,7 @@ int lib_cache(lua_State *L)
                { "max_ttl", cache_max_ttl },
                { "min_ttl", cache_min_ttl },
                { "ns_tout", cache_ns_tout },
+               { "zone_import", cache_zone_import },
                { NULL, NULL }
        };
 
index cf28be0ff010c8e11a22bea21914d6454ef93962..3d655f0ceed9eee1d7e6f32cdd1c1a8cdfe09441 100644 (file)
@@ -7,6 +7,7 @@ kresd_SOURCES := \
        daemon/ffimodule.c   \
        daemon/tls.c         \
        daemon/tls_ephemeral_credentials.c \
+       daemon/zimport.c     \
        daemon/main.c
 
 kresd_DIST := daemon/lua/kres.lua daemon/lua/kres-gen.lua \
index 4392dbd001dd72527d3cebb3b038eedce31f5604..772d68e59fe02107de686299b54a0028b72db3af 100644 (file)
@@ -35,6 +35,7 @@
 #include "daemon/engine.h"
 #include "daemon/io.h"
 #include "daemon/tls.h"
+#include "daemon/zimport.h"
 
 #define VERBOSE_MSG(qry, fmt...) QRVERBOSE(qry, "wrkr", fmt)
 
@@ -2385,6 +2386,11 @@ struct kr_request *worker_task_request(struct qr_task *task)
        return &task->ctx->req;
 }
 
+int worker_task_finalize(struct qr_task *task, int state)
+{
+       return qr_task_finalize(task, state);
+}
+
 void worker_session_close(struct session *session)
 {
        session_close(session);
@@ -2434,6 +2440,10 @@ void worker_reclaim(struct worker_ctx *worker)
        worker->subreq_out = NULL;
        map_clear(&worker->tcp_connected);
        map_clear(&worker->tcp_waiting);
+       if (worker->z_import != NULL) {
+               zi_free(worker->z_import);
+               worker->z_import = NULL;
+       }
 }
 
 struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
index 7d50c74df0cbe6b8c8e736ac68bb70c9afbf28aa..7a2d17cd202ce18162978708f95d6e454bf16bdd 100644 (file)
@@ -29,6 +29,8 @@ struct qr_task;
 struct worker_ctx;
 /** Transport session (opaque). */
 struct session;
+/** Zone import context (opaque). */
+struct zone_import_ctx;
 
 /** Create and initialize the worker. */
 struct worker_ctx *worker_create(struct engine *engine, knot_mm_t *pool,
@@ -140,6 +142,7 @@ struct worker_ctx {
                size_t timeout;
        } stats;
 
+       struct zone_import_ctx* z_import;
        bool too_many_open;
        size_t rconcurrent_highwatermark;
        /** List of active outbound TCP sessions */
diff --git a/daemon/zimport.c b/daemon/zimport.c
new file mode 100644 (file)
index 0000000..ebf95ed
--- /dev/null
@@ -0,0 +1,704 @@
+/*  Copyright (C) 2018 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/>.
+ */
+
+#include <stdlib.h>
+#include <uv.h>
+#include <ucw/mempool.h>
+#include <libknot/rrset.h>
+#include <zscanner/scanner.h>
+
+#include "lib/utils.h"
+#include "lib/dnssec/ta.h"
+#include "daemon/worker.h"
+#include "daemon/zimport.h"
+#include "lib/generic/map.h"
+#include "lib/generic/array.h"
+
+#define VERBOSE_MSG(qry, fmt...) QRVERBOSE(qry, "zimport", fmt)
+
+#define ZONE_IMPORT_PAUSE 100
+
+typedef array_t(knot_rrset_t *) qr_rrsetlist_t;
+
+struct zone_import_ctx {
+       struct worker_ctx *worker;
+       bool started;
+       knot_dname_t *origin;
+       knot_rrset_t *ta;
+       knot_rrset_t *key;
+       uint64_t start_timestamp;
+       size_t rrset_idx;
+       uv_timer_t timer;
+       map_t rrset_sorted;
+       qr_rrsetlist_t rrset_list;
+       knot_mm_t pool;
+       zi_callback cb;
+       void *cb_param;
+};
+
+typedef struct zone_import_ctx zone_import_ctx_t;
+
+int worker_task_finalize(struct qr_task *task, int state);
+
+static int RRSET_IS_ALREADY_IMPORTED = 1;
+
+/** @internal Allocate zone import context.
+ * @return pointer to zone import context or NULL. */
+static zone_import_ctx_t *zi_ctx_alloc()
+{
+       return (zone_import_ctx_t *)malloc(sizeof(zone_import_ctx_t));
+}
+
+/** @internal Free zone import context. */
+static void zi_ctx_free(zone_import_ctx_t *z_import)
+{
+       if (z_import != NULL) {
+               free(z_import);
+       }
+}
+
+/** @internal Reset all fields in the zone import context to their default values.
+ * Flushes memory pool, but doesn't reallocate memory pool buffer.
+ * Doesn't affect timer handle, pointers to callback and callback parameter.
+ * @return 0 if success; -1 if failed. */
+static int zi_reset(struct zone_import_ctx *z_import, size_t rrset_list_size)
+{
+       mp_flush(z_import->pool.ctx);
+
+       z_import->started = false;
+       z_import->start_timestamp = 0;
+       z_import->rrset_idx = 0;
+       z_import->pool.alloc = (knot_mm_alloc_t) mp_alloc;
+       z_import->rrset_sorted = map_make(&z_import->pool);
+
+       array_init(z_import->rrset_list);
+
+       int ret = 0;
+       if (rrset_list_size) {
+               ret = array_reserve_mm(z_import->rrset_list, rrset_list_size,
+                                      kr_memreserve, &z_import->pool);
+       }
+
+       return ret;
+}
+
+/** @internal Close callback for timer handle.
+ * @note Actually frees zone import context. */
+static void on_timer_close(uv_handle_t *handle)
+{
+       zone_import_ctx_t *z_import = (zone_import_ctx_t *)handle->data;
+       if (z_import != NULL) {
+               zi_ctx_free(z_import);
+       }
+}
+
+zone_import_ctx_t *zi_allocate(struct worker_ctx *worker,
+                              zi_callback cb, void *param)
+{
+       if (worker->loop == NULL) {
+               return NULL;
+       }
+       zone_import_ctx_t *z_import = zi_ctx_alloc();
+       if (!z_import) {
+               return NULL;
+       }
+       void *mp = mp_new (8192);
+       if (!mp) {
+               zi_ctx_free(z_import);
+               return NULL;
+       }
+       memset(z_import, 0, sizeof(*z_import));
+       z_import->pool.ctx = mp;
+       z_import->worker = worker;
+       int ret = zi_reset(z_import, 0);
+       if (ret < 0) {
+               mp_delete(mp);
+               zi_ctx_free(z_import);
+               z_import = NULL;
+       }
+       uv_timer_init(z_import->worker->loop, &z_import->timer);
+       z_import->timer.data = z_import;
+       z_import->cb = cb;
+       z_import->cb_param = param;
+       return z_import;
+}
+
+void zi_free(zone_import_ctx_t *z_import)
+{
+       z_import->started = false;
+       z_import->start_timestamp = 0;
+       z_import->rrset_idx = 0;
+       mp_delete(z_import->pool.ctx);
+       z_import->pool.ctx = NULL;
+       z_import->pool.alloc = NULL;
+       z_import->worker = NULL;
+       z_import->cb = NULL;
+       z_import->cb_param = NULL;
+       uv_close((uv_handle_t *)&z_import->timer, on_timer_close);
+}
+
+/** @internal Mark rrset that has been already imported
+ *  to avoid repeated import. */
+static inline void zi_rrset_mark_as_imported(knot_rrset_t *rr)
+{
+       rr->additional = (void *)&RRSET_IS_ALREADY_IMPORTED;
+}
+
+/** @internal Check if rrset is marked as "already imported".
+ * @return true if marked, false if isn't */
+static inline bool zi_rrset_is_marked_as_imported(knot_rrset_t *rr)
+{
+       return (rr->additional == &RRSET_IS_ALREADY_IMPORTED);
+}
+
+/** @internal Try to find rrset with required properties amongst parsed rrsets
+ * and put it to given packet, If rrset found and successfully put, it marked as
+ * "already imported" to avoid repeated import.
+ * @return -1 if failed
+ *          0 if required record been actually put into the packet
+ *          1 if required record could not be found */
+static int zi_put_supplementary(struct zone_import_ctx *z_import,
+                               knot_pkt_t *pkt, const knot_dname_t *owner,
+                               uint16_t class, uint16_t supp_type)
+{
+       assert(supp_type != KNOT_RRTYPE_RRSIG);
+
+       char key[KR_RRKEY2_LEN];
+       int err = kr_rrkey2(key, class, owner, supp_type, supp_type);
+       if (err <= 0) {
+               return -1;
+       }
+       knot_rrset_t *additional_rr = map_get(&z_import->rrset_sorted, key);
+       err = kr_rrkey2(key, class, owner, KNOT_RRTYPE_RRSIG, supp_type);
+       if (err <= 0) {
+               return -1;
+       }
+       knot_rrset_t *rrsig = map_get(&z_import->rrset_sorted, key);
+       if (additional_rr) {
+               err = knot_pkt_put(pkt, 0, additional_rr, 0);
+               if (err != KNOT_EOK) {
+                       return -1;
+               }
+               zi_rrset_mark_as_imported(additional_rr);
+       }
+       if (rrsig) {
+               err = knot_pkt_put(pkt, 0, rrsig, 0);
+               if (err != KNOT_EOK) {
+                       return -1;
+               }
+               zi_rrset_mark_as_imported(rrsig);
+       }
+       return additional_rr == NULL ? 1 : 0;
+}
+
+/** @internal Import given rrset to cache.
+ * The main goal of import procedure is to store parsed records to the cache.
+ * Resolver imports rrset by creating request that will never be sent to upstream.
+ * After request creation resolver creates pseudo-answer which must contain
+ * all necessary data for validation. Then resolver process answer as if he had
+ * been received from network.
+ * @return -1 if failed; 0 if success */
+static int zi_rrset_import(zone_import_ctx_t *z_import, knot_rrset_t *rr)
+{
+       struct worker_ctx *worker = z_import->worker;
+
+       assert(worker);
+
+       knot_mm_t *pool = &z_import->pool;
+
+       uint8_t *dname = rr->owner;
+       uint16_t rrtype = rr->type;
+       uint16_t rrclass = rr->rclass;
+
+       struct kr_qflags options;
+       memset(&options, 0, sizeof(options));
+       options.DNSSEC_WANT = true;
+       options.NO_MINIMIZE = true;
+       uint32_t msgid = kr_rand_uint(0);
+
+       /* Create query */
+       knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
+       if (!query) {
+               return -1;
+       }
+       knot_pkt_t *answer = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
+       if (!answer) {
+               knot_pkt_free(&query);
+               return -1;
+       }
+
+       knot_pkt_put_question(query, dname, rrclass, rrtype);
+       knot_pkt_begin(query, KNOT_ANSWER);
+       knot_wire_set_rd(query->wire);
+       knot_wire_set_id(query->wire, msgid);
+
+       struct qr_task *task = worker_resolve_start(worker, query, options);
+       if (!task) {
+               knot_pkt_free(&query);
+               knot_pkt_free(&answer);
+               return -1;
+       }
+
+       struct kr_request *request = worker_task_request(task);
+       struct kr_rplan *rplan = &request->rplan;
+       struct kr_query *qry = kr_rplan_push(rplan, NULL, dname, rrclass, rrtype);
+       char key[KR_RRKEY2_LEN];
+       int err = 0;
+       int state = KR_STATE_FAIL;
+       bool origin_is_owner = knot_dname_is_equal(rr->owner, z_import->origin);
+       bool is_referral = (rrtype == KNOT_RRTYPE_NS && !origin_is_owner);
+
+       qry->id = msgid;
+       kr_zonecut_init(&qry->zone_cut, z_import->origin, pool);
+       qry->zone_cut.key = z_import->key;
+       qry->zone_cut.trust_anchor = z_import->ta;
+
+       if (knot_pkt_init_response(request->answer, query) != 0) {
+               goto cleanup;
+       }
+
+       knot_pkt_put_question(answer, dname, rrclass, rrtype);
+       knot_pkt_begin(answer, KNOT_ANSWER);
+
+       if (is_referral) {
+               knot_pkt_begin(answer, KNOT_AUTHORITY);
+       }
+
+       err = knot_pkt_put(answer, 0, rr, 0);
+       if (err != 0) {
+               goto cleanup;
+       }
+       zi_rrset_mark_as_imported(rr);
+
+       err = kr_rrkey2(key, rr->rclass, rr->owner, KNOT_RRTYPE_RRSIG, rr->type);
+       if (err <= 0) {
+               goto cleanup;
+       }
+       knot_rrset_t *rrsig = map_get(&z_import->rrset_sorted, key);
+       if (rrsig) {
+               err = knot_pkt_put(answer, 0, rrsig, 0);
+               if (err != 0) {
+                       goto cleanup;
+               }
+               zi_rrset_mark_as_imported(rrsig);
+       }
+
+       if (!is_referral) {
+               knot_wire_set_aa(answer->wire);
+       } else {
+               /* Type is KNOT_RRTYPE_NS and owner is not equal to origin.
+                * It will be "referral" answer, so try to add DS or NSEC* to it. */
+               err = zi_put_supplementary(z_import, answer, rr->owner,
+                                          rr->rclass, KNOT_RRTYPE_DS);
+               if (err < 0) {
+                       goto cleanup;
+               } else if (err == 1) {
+                       /* DS not found */
+                       err = zi_put_supplementary(z_import, answer, rr->owner,
+                                                  rr->rclass, KNOT_RRTYPE_NSEC);
+                       if (err < 0) {
+                               goto cleanup;
+                       }
+                       err = zi_put_supplementary(z_import, answer, rr->owner,
+                                                  rr->rclass, KNOT_RRTYPE_NSEC3);
+                       if (err < 0) {
+                               goto cleanup;
+                       }
+               }
+       }
+
+       knot_pkt_begin(answer, KNOT_ADDITIONAL);
+
+       if (rrtype == KNOT_RRTYPE_NS) {
+               /* Try to find glue addresses. */
+               for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
+                       const knot_dname_t *ns_name = knot_ns_name(&rr->rrs, i);
+                       err = zi_put_supplementary(z_import, answer, ns_name,
+                                                  rr->rclass, KNOT_RRTYPE_A);
+                       if (err < 0) {
+                               goto cleanup;
+                       }
+
+                       err = zi_put_supplementary(z_import, answer, ns_name,
+                                                  rr->rclass, KNOT_RRTYPE_AAAA);
+                       if (err < 0) {
+                               goto cleanup;
+                       }
+               }
+       }
+
+       knot_wire_set_id(answer->wire, msgid);
+       answer->parsed = answer->size;
+
+       /* Importing doesn't imply communication with upstream at all.
+        * "answer" contains pseudo-answer from upstream and must be successfully
+        * validated in CONSUME stage. If not, something gone wrong. */
+       state = kr_resolve_consume(request, NULL, answer);
+
+cleanup:
+
+       knot_pkt_free(&query);
+       knot_pkt_free(&answer);
+       worker_task_finalize(task, state);
+       return state == (is_referral ? KR_STATE_PRODUCE : KR_STATE_DONE) ? 0 : -1;
+}
+
+/** @internal Create element in qr_rrsetlist_t rrset_list for
+ * given node of map_t rrset_sorted.  */
+static int zi_mapwalk_preprocess(const char *k, void *v, void *baton)
+{
+       zone_import_ctx_t *z_import = (zone_import_ctx_t *)baton;
+
+       int ret = array_push_mm(z_import->rrset_list, v, kr_memreserve, &z_import->pool);
+
+       return (ret < 0);
+}
+
+/** @internal Iterate over parsed rrsets and try to import each of them. */
+static void zi_zone_process(uv_timer_t* handle)
+{
+       zone_import_ctx_t *z_import = (zone_import_ctx_t *)handle->data;
+
+       assert(z_import->worker);
+
+       size_t failed = 0;
+       size_t ns_imported = 0;
+       size_t other_imported = 0;
+
+       /* At the moment import of root zone only is supported.
+        * Check the name of the parsed zone.
+        * TODO - implement importing of arbitrary zone. */
+       char zone_name_str[KNOT_DNAME_MAXLEN];
+       knot_dname_to_str(zone_name_str, z_import->origin, sizeof(zone_name_str));
+       if (strcmp(".", zone_name_str) != 0) {
+               kr_log_error("[zimport] unexpected zone name `%s` (root zone expected), fail\n",
+                            zone_name_str);
+               failed = 1;
+               goto finish;
+       }
+
+       if (z_import->rrset_list.len <= 0) {
+               VERBOSE_MSG(NULL, "zone is empty\n");
+               goto finish;
+       }
+
+       /* z_import.rrset_list now contains sorted rrset list.
+        * Records are sorted by the key returned by kr_rrkey() function.
+        * Find out if zone is secured.
+        * Try to find TA for worker->z_import.origin. */
+       map_t *trust_anchors = &z_import->worker->engine->resolver.trust_anchors;
+       knot_rrset_t *rr = kr_ta_get(trust_anchors, z_import->origin);
+       if (!rr) {
+               /* For now - fail.
+                * TODO - query DS and continue after answer had been obtained. */
+               kr_log_error("[zimport] TA not found for `%s`, fail\n", zone_name_str);
+               failed = 1;
+               goto finish;
+       }
+       z_import->ta = rr;
+
+       /* TA have been found, zone is secured.
+        * DNSKEY must be somewhere amongst the imported records. Find it.
+        * TODO - For those zones that provenly do not have TA this step must be skipped. */
+       char key[KR_RRKEY2_LEN];
+       int err = kr_rrkey2(key, KNOT_CLASS_IN, z_import->origin,
+                           KNOT_RRTYPE_DNSKEY, KNOT_RRTYPE_DNSKEY);
+       if (err <= 0) {
+               failed = 1;
+               goto finish;
+       }
+
+       rr = map_get(&z_import->rrset_sorted, key);
+       if (!rr) {
+               /* DNSKEY MUST be here. If not found - fail. */
+               kr_log_error("[zimport] DNSKEY not found for `%s`, fail\n", zone_name_str);
+               failed = 1;
+               goto finish;
+       }
+       z_import->key = rr;
+
+       VERBOSE_MSG(NULL, "started: zone: '%s'\n", zone_name_str);
+
+       z_import->start_timestamp = kr_now();
+
+       /* Import DNSKEY at first step. If any validation problems will appear,
+        * cancel import of whole zone. */
+       char qname_str[KNOT_DNAME_MAXLEN], type_str[16];
+       knot_dname_to_str(qname_str, rr->owner, sizeof(qname_str));
+       knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
+       VERBOSE_MSG(NULL, "importing: qname: '%s' type: '%s'\n",
+                   qname_str, type_str);
+
+       int res = zi_rrset_import(z_import, rr);
+       if (res != 0) {
+               VERBOSE_MSG(NULL, "import failed: qname: '%s' type: '%s'\n",
+                           qname_str, type_str);
+               failed = 1;
+               goto finish;
+       }
+
+       /* Import all NS records */
+       for (size_t i = 0; i < z_import->rrset_list.len; ++i) {
+               knot_rrset_t *rr = z_import->rrset_list.at[i];
+
+               if (rr->type != KNOT_RRTYPE_NS) {
+                       continue;
+               }
+
+               knot_dname_to_str(qname_str, rr->owner, sizeof(qname_str));
+               knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
+               VERBOSE_MSG(NULL, "importing: qname: '%s' type: '%s'\n",
+                           qname_str, type_str);
+               int res = zi_rrset_import(z_import, rr);
+               if (res == 0) {
+                       ++ns_imported;
+               } else {
+                       VERBOSE_MSG(NULL, "import failed: qname: '%s' type: '%s'\n",
+                                   qname_str, type_str);
+                       ++failed;
+               }
+               z_import->rrset_list.at[i] = NULL;
+       }
+
+       /* NS records have been imported as well as relative DS, NSEC* and glue.
+        * Now import what's left. */
+       for (size_t i = 0; i < z_import->rrset_list.len; ++i) {
+
+               knot_rrset_t *rr = z_import->rrset_list.at[i];
+               if (rr == NULL) {
+                       continue;
+               }
+
+               if (zi_rrset_is_marked_as_imported(rr)) {
+                       continue;
+               }
+
+               if (rr->type == KNOT_RRTYPE_DNSKEY || rr->type == KNOT_RRTYPE_RRSIG) {
+                       continue;
+               }
+
+               knot_dname_to_str(qname_str, rr->owner, sizeof(qname_str));
+               knot_rrtype_to_string(rr->type, type_str, sizeof(type_str));
+               VERBOSE_MSG(NULL, "importing: qname: '%s' type: '%s'\n",
+                           qname_str, type_str);
+               res = zi_rrset_import(z_import, rr);
+               if (res == 0) {
+                       ++other_imported;
+               } else {
+                       VERBOSE_MSG(NULL, "import failed: qname: '%s' type: '%s'\n",
+                                   qname_str, type_str);
+                       ++failed;
+               }
+       }
+
+       uint64_t elapsed = kr_now() - z_import->start_timestamp;
+       elapsed = elapsed > UINT_MAX ? UINT_MAX : elapsed;
+
+       VERBOSE_MSG(NULL, "finished in %lu ms; zone: `%s`; ns: %zd; other: %zd; failed: %zd\n",
+                   elapsed, zone_name_str, ns_imported, other_imported, failed);
+
+finish:
+
+       uv_timer_stop(&z_import->timer);
+       z_import->started = false;
+
+       int import_state = 0;
+
+       if (failed != 0) {
+               if (ns_imported == 0 && other_imported == 0) {
+                       import_state = -1;
+                       VERBOSE_MSG(NULL, "import failed; zone `%s` \n", zone_name_str);
+               } else {
+                       import_state = 1;
+               }
+       } else {
+               import_state = 0;
+       }
+
+       if (z_import->cb != NULL) {
+               z_import->cb(import_state, z_import->cb_param);
+       }
+}
+
+/** @internal Store rrset that has been imported to zone import context memory pool.
+ * @return -1 if failed; 0 if success. */
+static int zi_record_store(zs_scanner_t *s)
+{
+       if (s->r_data_length > UINT16_MAX) {
+               /* Due to knot_rrset_add_rdata(..., const uint16_t size, ...); */
+               kr_log_error("[zscanner] line %lu: rdata is too long\n", s->line_counter);
+               return -1;
+       }
+
+       if (knot_dname_size(s->r_owner) != strlen((const char *)(s->r_owner)) + 1) {
+               kr_log_error("[zscanner] line %lu: owner name contains zero byte, skip\n", s->line_counter);
+               return 0;
+       }
+
+       zone_import_ctx_t *z_import = (zone_import_ctx_t *)s->process.data;
+
+       knot_rrset_t *new_rr = knot_rrset_new(s->r_owner, s->r_type, s->r_class,
+                                             &z_import->pool);
+       if (!new_rr) {
+               kr_log_error("[zscanner] line %lu: error creating rrset\n", s->line_counter);
+               return -1;
+       }
+       int ret = knot_rrset_add_rdata(new_rr, s->r_data, s->r_data_length,
+                                      s->r_ttl, &z_import->pool);
+       if (ret != KNOT_EOK) {
+               kr_log_error("[zscanner] line %lu: error adding rdata to rrset\n", s->line_counter);
+               return -1;
+       }
+
+       char key[KR_RRKEY2_LEN];
+       uint16_t additional_key_field = kr_rrset_type_maysig(new_rr);
+
+       ret = kr_rrkey2(key, new_rr->rclass, new_rr->owner, new_rr->type,
+                       additional_key_field);
+       if (ret <= 0) {
+               kr_log_error("[zscanner] line %lu: error constructing rrkey\n", s->line_counter);
+               return -1;
+       }
+
+       knot_rrset_t *saved_rr = map_get(&z_import->rrset_sorted, key);
+       if (saved_rr) {
+               ret = knot_rdataset_merge(&saved_rr->rrs, &new_rr->rrs,
+                                         &z_import->pool);
+       } else {
+               ret = map_set(&z_import->rrset_sorted, key, new_rr);
+       }
+       if (ret != 0) {
+               kr_log_error("[zscanner] line %lu: error saving parsed rrset\n", s->line_counter);
+               return -1;
+       }
+
+       return 0;
+}
+
+/** @internal zscanner callback. */
+static int zi_state_parsing(zs_scanner_t *s)
+{
+       while (zs_parse_record(s) == 0) {
+               switch (s->state) {
+               case ZS_STATE_DATA:
+                       if (zi_record_store(s) != 0) {
+                               return -1;
+                       }
+                       zone_import_ctx_t *z_import = (zone_import_ctx_t *) s->process.data;
+                       if (z_import->origin == 0) {
+                               z_import->origin = knot_dname_copy(s->zone_origin,
+                                                                 &z_import->pool);
+                       } else if (!knot_dname_is_equal(z_import->origin, s->zone_origin)) {
+                               kr_log_error("[zscanner] line: %lu: zone origin changed unexpectedly\n",
+                                            s->line_counter);
+                               return -1;
+                       }
+                       break;
+               case ZS_STATE_ERROR:
+                       kr_log_error("[zscanner] line: %lu: error parsing record\n", s->line_counter);
+                       return -1;
+                       break;
+               case ZS_STATE_INCLUDE:
+                       return -1;
+                       break;
+               default:
+                       return (s->error.counter == 0) ? 0 : -1;
+               }
+       }
+
+       return -1;
+}
+
+int zi_zone_import(struct zone_import_ctx *z_import,
+                  const char *zone_file, const char *origin,
+                  uint16_t rclass, uint32_t ttl)
+{
+       if (z_import->worker == NULL) {
+               kr_log_error("[zscanner] invalid <z_import> parameter\n");
+               return -1;
+       }
+
+       zs_scanner_t *s = malloc(sizeof(zs_scanner_t));
+       if (s == NULL) {
+               kr_log_error("[zscanner] error creating instance of zone scanner\n");
+               return -1;
+       }
+
+       if (zs_init(s, origin, rclass, ttl) != 0) {
+               free(s);
+               kr_log_error("[zscanner] error initializing zone scanner instance\n");
+               return -1;
+       }
+
+       if (zs_set_input_file(s, zone_file) != 0) {
+               zs_deinit(s);
+               free(s);
+               kr_log_error("[zscanner] error opening zone file `%s`\n", zone_file);
+               return -1;
+       }
+
+       /* Don't set callbacks as we don't use automatic parsing.
+        * Store pointer to zone import context. */
+       if (zs_set_processing(s, NULL, NULL, (void *)z_import) != 0) {
+               zs_deinit(s);
+               free(s);
+               kr_log_error("[zscanner] error opening zone file `%s`\n", zone_file);
+               return -1;
+       }
+
+       /* To reduce time spent in the callback, import is split
+        * into two stages. In the first stage zone file is parsed and prepared
+        * for importing to cache. In the second stage parsed zone is imported
+        * into the cache. Since root zone isn't large it is imported as single
+        * chunk. If it would be considered as necessary, second stage can be
+        * split into shorter stages. */
+
+       uint64_t elapsed = 0;
+       int ret = zi_reset(z_import, 4096);
+       if (ret == 0) {
+               z_import->started = true;
+               z_import->start_timestamp = kr_now();
+               VERBOSE_MSG(NULL, "[zscanner] started; zone file `%s`\n",
+                           zone_file);
+               ret = zi_state_parsing(s);
+               elapsed = kr_now() - z_import->start_timestamp;
+               elapsed = elapsed > UINT_MAX ? UINT_MAX : elapsed;
+       }
+       zs_deinit(s);
+       free(s);
+
+       if (ret != 0) {
+               kr_log_error("[zscanner] error parsing zone file `%s`\n", zone_file);
+               z_import->started = false;
+               return ret;
+       }
+
+       VERBOSE_MSG(NULL, "[zscanner] finished in %lu ms; zone file `%s`\n",
+                           elapsed, zone_file);
+
+       map_walk(&z_import->rrset_sorted, zi_mapwalk_preprocess, z_import);
+
+       /* Start import */
+       uv_timer_start(&z_import->timer, zi_zone_process,
+                      ZONE_IMPORT_PAUSE, ZONE_IMPORT_PAUSE);
+
+       return 0;
+}
+
+bool zi_import_started(struct zone_import_ctx *z_import)
+{
+       return z_import ? z_import->started : false;
+}
diff --git a/daemon/zimport.h b/daemon/zimport.h
new file mode 100644 (file)
index 0000000..57b246e
--- /dev/null
@@ -0,0 +1,68 @@
+/*  Copyright (C) 2018 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/>.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+struct worker_ctx;
+/** Zone import context (opaque).  */
+struct zone_import_ctx;
+
+/**
+ * Completion callback
+ *
+ * @param state -1 - fail
+ *               0 - success
+ *               1 - success, but there are non-critical errors
+ * @param pointer to user data
+ */
+typedef void (*zi_callback)(int state, void *param);
+
+/**
+ * Allocate and initialize zone import context.
+ *
+ * @param worker pointer to worker state
+ * @return NULL or pointer to zone import context.
+ */
+struct zone_import_ctx *zi_allocate(struct worker_ctx *worker,
+                                   zi_callback cb, void *param);
+
+/** Free zone import context. */
+void zi_free(struct zone_import_ctx *z_import);
+
+/**
+ * Import zone from file.
+ *
+ * @note only root zone import is supported; origin must be NULL or "."
+ * @param z_import pointer to zone import context
+ * @param zone_file zone file name
+ * @param origin default origin
+ * @param rclass default class
+ * @param ttl    default ttl
+ * @return 0 or an error code
+ */
+int zi_zone_import(struct zone_import_ctx *z_import,
+                  const char *zone_file, const char *origin,
+                  uint16_t rclass, uint32_t ttl);
+
+/**
+ * Check if import already in process.
+ *
+ * @param z_import pointer to zone import context.
+ * @return true if import already in process; false otherwise.
+ */
+bool zi_import_started(struct zone_import_ctx *z_import);
index fb10c0cd13462aa36192223efb5115f629fbc480..ece4dd147667162ecd412db00e7b37c4f1a2b855 100644 (file)
@@ -124,14 +124,6 @@ static inline void array_std_free(void *baton, void *p)
 #define array_reserve_mm(array, n, reserve, baton) \
        (reserve)((baton), (char **) &(array).at, sizeof((array).at[0]), (n), &(array).cap)
 
-/**
- * Push value at the end of the array, resize it if necessary (plain malloc/free).
- * @note May fail if the capacity is not reserved.
- * @return element index on success, <0 on failure
- */
-#define array_push(array, val) \
-       array_push_mm(array, val, array_std_reserve, NULL)
-
 /**
  * Push value at the end of the array, resize it if necessary.
  * Mempool usage: pass kr_memreserve and a knot_mm_t* .
@@ -143,6 +135,14 @@ static inline void array_std_free(void *baton, void *p)
                : (array_reserve_mm(array, ((array).cap + 1), reserve, baton) < 0 ? -1 \
                        : ((array).at[(array).len] = val, (array).len++)))
 
+/**
+ * Push value at the end of the array, resize it if necessary (plain malloc/free).
+ * @note May fail if the capacity is not reserved.
+ * @return element index on success, <0 on failure
+ */
+#define array_push(array, val) \
+       array_push_mm(array, val, array_std_reserve, NULL)
+
 /**
  * Pop value from the end of the array.
  */
index 922ac3a9b1cb9b2624223dae17f87686da458a71..d643ee71e58e5728018adc8331333ef1d100c047 100644 (file)
@@ -600,6 +600,37 @@ int kr_rrkey(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank)
        return (char *)&key_buf[ret] - key;
 }
 
+int kr_rrkey2(char *key, uint16_t class, const knot_dname_t *owner,
+             uint16_t type, uint16_t additional)
+{
+       if (!key || !owner) {
+               return kr_error(EINVAL);
+       }
+       uint8_t *key_buf = (uint8_t *)key;
+       int ret = u16tostr(key_buf, class);
+       if (ret <= 0) {
+               return ret;
+       }
+       key_buf += ret;
+       ret = knot_dname_to_wire(key_buf, owner, KNOT_DNAME_MAXLEN);
+       if (ret <= 0) {
+               return ret;
+       }
+       knot_dname_to_lower(key_buf);
+       key_buf += ret - 1;
+       ret = u16tostr(key_buf, type);
+       if (ret <= 0) {
+               return ret;
+       }
+       key_buf += ret;
+       ret = u16tostr(key_buf, additional);
+       if (ret <= 0) {
+               return ret;
+       }
+       key_buf[ret] = '\0';
+       return (char *)&key_buf[ret] - key;
+}
+
 /** Return whether two RRsets match, i.e. would form the same set; see ranked_rr_array_t */
 static inline bool rrsets_match(const knot_rrset_t *rr1, const knot_rrset_t *rr2)
 {
index 306830cae6699a8fed3c07fb266b1921f21e65e2..ff8a0a69eb9256ccfe63f01e2c60fe1a3c67f824 100644 (file)
@@ -298,6 +298,21 @@ static inline bool KEY_COVERING_RRSIG(const char *key)
 KR_EXPORT
 int kr_rrkey(char *key, const knot_dname_t *owner, uint16_t type, uint8_t rank);
 
+/* Stash key = {[5] class, [1-255] owner, [5] type, [5] additional, [1] \x00 } */
+#define KR_RRKEY2_LEN (16 + KNOT_DNAME_MAXLEN)
+/** Create unique null-terminated string key for RR.
+  * @param key Destination buffer for key size, MUST be KR_RRKEY2_LEN or larger.
+  * @param class RR class.
+  * @param owner RR owner name.
+  * @param type RR type.
+  * @param additional flags (for instance can be used for storing covered type
+  *       when RR type is RRSIG).
+  * @return key length if successful or an error
+  * */
+KR_EXPORT
+int kr_rrkey2(char *key, uint16_t class, const knot_dname_t *owner,
+             uint16_t type, uint16_t additional);
+
 /** @internal Add RRSet copy to ranked RR array. */
 KR_EXPORT
 int kr_ranked_rrarray_add(ranked_rr_array_t *array, const knot_rrset_t *rr,