zimport.c 23.7 KB
Newer Older
Grigorii Demidov's avatar
Grigorii Demidov committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  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/>.
 */

17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
/* Module is intended to import resource records from file into resolver's cache.
 * File supposed to be a standard DNS zone file
 * which contains text representations of resource records.
 * For now only root zone import is supported.
 *
 * Import process consists of two stages.
 * 1) Zone file parsing.
 * 2) Import of parsed entries into the cache.
 *
 * These stages are implemented as two separate functions
 * (zi_zone_import and zi_zone_process) which runs sequentially with the
 * pause between them. This is done because resolver is a single-threaded
 * application, so it can't process user's requests during the whole import
 * process. Separation into two stages allows to reduce the
 * continuous time interval when resolver can't serve user requests.
 * Since root zone isn't large it is imported as single
 * chunk. If it would be considered as necessary, import stage can be
 * split into shorter stages.
 *
 * zi_zone_import() uses libzscanner to parse zone file.
 * Parsed records are stored to internal storage from where they are imported to
 * cache during the second stage.
 *
 * zi_zone_process() imports parsed resource records to cache.
 * It 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.
 */

47
#include <inttypes.h> /* PRIu64 */
Grigorii Demidov's avatar
Grigorii Demidov committed
48 49 50 51
#include <stdlib.h>
#include <uv.h>
#include <ucw/mempool.h>
#include <libknot/rrset.h>
Vladimír Čunát's avatar
Vladimír Čunát committed
52
#include <libzscanner/scanner.h>
Grigorii Demidov's avatar
Grigorii Demidov committed
53 54 55 56 57 58 59 60

#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"

61
#define VERBOSE_MSG(qry, ...) QRVERBOSE(qry, "zimport", __VA_ARGS__)
Grigorii Demidov's avatar
Grigorii Demidov committed
62

63 64
/* Pause between parse and import stages, milliseconds.
 * See comment in zi_zone_import() */
Grigorii Demidov's avatar
Grigorii Demidov committed
65 66 67 68 69 70 71 72 73 74 75 76 77
#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;
78 79
	map_t rrset_indexed;
	qr_rrsetlist_t rrset_sorted;
Grigorii Demidov's avatar
Grigorii Demidov committed
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	knot_mm_t pool;
	zi_callback cb;
	void *cb_param;
};

typedef struct zone_import_ctx zone_import_ctx_t;

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. */
108
static int zi_reset(struct zone_import_ctx *z_import, size_t rrset_sorted_list_size)
Grigorii Demidov's avatar
Grigorii Demidov committed
109 110 111 112 113 114 115
{
	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;
116
	z_import->rrset_indexed = map_make(&z_import->pool);
Grigorii Demidov's avatar
Grigorii Demidov committed
117

118
	array_init(z_import->rrset_sorted);
Grigorii Demidov's avatar
Grigorii Demidov committed
119 120

	int ret = 0;
121 122
	if (rrset_sorted_list_size) {
		ret = array_reserve_mm(z_import->rrset_sorted, rrset_sorted_list_size,
Grigorii Demidov's avatar
Grigorii Demidov committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
				       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);
161
		return NULL;
Grigorii Demidov's avatar
Grigorii Demidov committed
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
	}
	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);
}

198 199 200 201
/** @internal Try to find rrset with given requisites amongst parsed rrsets
 * and put it to given packet. If there is RRSIG which covers that rrset, it
 * will be added as well. If rrset found and successfully put, it marked as
 * "already imported" to avoid repeated import. The same is true for RRSIG.
Grigorii Demidov's avatar
Grigorii Demidov committed
202 203 204
 * @return -1 if failed
 *          0 if required record been actually put into the packet
 *          1 if required record could not be found */
205 206 207
static int zi_rrset_find_put(struct zone_import_ctx *z_import,
			     knot_pkt_t *pkt, const knot_dname_t *owner,
			     uint16_t class, uint16_t type, uint16_t additional)
Grigorii Demidov's avatar
Grigorii Demidov committed
208
{
209 210 211 212
	if (type != KNOT_RRTYPE_RRSIG) {
		/* If required rrset isn't rrsig, these must be the same values */
		additional = type;
	}
Grigorii Demidov's avatar
Grigorii Demidov committed
213

214
	char key[KR_RRKEY_LEN];
215
	int err = kr_rrkey(key, class, owner, type, additional);
Grigorii Demidov's avatar
Grigorii Demidov committed
216 217 218
	if (err <= 0) {
		return -1;
	}
219 220 221 222 223 224
	knot_rrset_t *rr = map_get(&z_import->rrset_indexed, key);
	if (!rr) {
		return 1;
	}
	err = knot_pkt_put(pkt, 0, rr, 0);
	if (err != KNOT_EOK) {
Grigorii Demidov's avatar
Grigorii Demidov committed
225 226
		return -1;
	}
227 228 229 230 231 232 233 234
	zi_rrset_mark_as_imported(rr);

	if (type != KNOT_RRTYPE_RRSIG) {
		/* Try to find corresponding rrsig */
		err = zi_rrset_find_put(z_import, pkt, owner,
					class, KNOT_RRTYPE_RRSIG, type);
		if (err < 0) {
			return err;
Grigorii Demidov's avatar
Grigorii Demidov committed
235 236
		}
	}
237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292

	return 0;
}

/** @internal Try to put given rrset to the given packet.
 * If there is RRSIG which covers that rrset, it will be added as well.
 * If rrset successfully put in the packet, it marked as
 * "already imported" to avoid repeated import.
 * The same is true for RRSIG.
 * @return -1 if failed
 *          0 if required record been actually put into the packet */
static int zi_rrset_put(struct zone_import_ctx *z_import, knot_pkt_t *pkt,
			knot_rrset_t *rr)
{
	assert(rr);
	assert(rr->type != KNOT_RRTYPE_RRSIG);
	int err = knot_pkt_put(pkt, 0, rr, 0);
	if (err != KNOT_EOK) {
		return -1;
	}
	zi_rrset_mark_as_imported(rr);
	/* Try to find corresponding RRSIG */
	err = zi_rrset_find_put(z_import, pkt, rr->owner, rr->rclass,
				KNOT_RRTYPE_RRSIG, rr->type);
	return (err < 0) ? err : 0;
}

/** @internal Try to put DS & NSEC* for rset->owner to given packet.
 * @return -1 if failed;
 *          0 if no errors occurred (it doesn't mean
 *            that records were actually added). */
static int zi_put_delegation(zone_import_ctx_t *z_import, knot_pkt_t *pkt,
			     knot_rrset_t *rr)
{
	int err = zi_rrset_find_put(z_import, pkt, rr->owner,
				    rr->rclass, KNOT_RRTYPE_DS, 0);
	if (err == 1) {
		/* DS not found, maybe there are NSEC* */
		err = zi_rrset_find_put(z_import, pkt, rr->owner,
					rr->rclass, KNOT_RRTYPE_NSEC, 0);
		if (err >= 0) {
			err = zi_rrset_find_put(z_import, pkt, rr->owner,
						rr->rclass, KNOT_RRTYPE_NSEC3, 0);
		}
	}
	return err < 0 ? err : 0;
}

/** @internal Try to put A & AAAA records for rset->owner to given packet.
 * @return -1 if failed;
 *          0 if no errors occurred (it doesn't mean
 *            that records were actually added). */
static int zi_put_glue(zone_import_ctx_t *z_import, knot_pkt_t *pkt,
			     knot_rrset_t *rr)
{
	int err = 0;
293 294 295 296
	knot_rdata_t *rdata_i = rr->rrs.rdata;
	for (uint16_t i = 0; i < rr->rrs.count;
			++i, rdata_i = knot_rdataset_next(rdata_i)) {
		const knot_dname_t *ns_name = knot_ns_name(rdata_i);
297 298 299 300 301 302 303 304 305 306
		err = zi_rrset_find_put(z_import, pkt, ns_name,
					rr->rclass, KNOT_RRTYPE_A, 0);
		if (err < 0) {
			break;
		}

		err = zi_rrset_find_put(z_import, pkt, ns_name,
					rr->rclass, KNOT_RRTYPE_AAAA, 0);
		if (err < 0) {
			break;
Grigorii Demidov's avatar
Grigorii Demidov committed
307 308
		}
	}
309 310 311 312 313 314 315 316
	return err < 0 ? err : 0;
}

/** @internal Create query. */
static knot_pkt_t *zi_query_create(zone_import_ctx_t *z_import, knot_rrset_t *rr)
{
	knot_mm_t *pool = &z_import->pool;

317
	uint32_t msgid = kr_rand_bytes(2);
318 319 320 321 322 323 324 325 326 327 328 329

	knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
	if (!query) {
		return NULL;
	}

	knot_pkt_put_question(query, rr->owner, rr->rclass, rr->type);
	knot_pkt_begin(query, KNOT_ANSWER);
	knot_wire_set_rd(query->wire);
	knot_wire_set_id(query->wire, msgid);
	int err = knot_pkt_parse(query, 0);
	if (err != KNOT_EOK) {
Vladimír Čunát's avatar
Vladimír Čunát committed
330
		knot_pkt_free(query);
331 332 333 334
		return NULL;
	}

	return query;
Grigorii Demidov's avatar
Grigorii Demidov committed
335 336 337 338 339 340 341 342 343 344
}

/** @internal Import given rrset to cache.
 * @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);

345 346 347 348 349
	/* Create "pseudo query" which asks for given rrset. */
	knot_pkt_t *query = zi_query_create(z_import, rr);
	if (!query) {
		return -1;
	}
Grigorii Demidov's avatar
Grigorii Demidov committed
350

351
	knot_mm_t *pool = &z_import->pool;
Grigorii Demidov's avatar
Grigorii Demidov committed
352 353 354 355
	uint8_t *dname = rr->owner;
	uint16_t rrtype = rr->type;
	uint16_t rrclass = rr->rclass;

356
	/* Create "pseudo answer". */
Grigorii Demidov's avatar
Grigorii Demidov committed
357 358
	knot_pkt_t *answer = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, pool);
	if (!answer) {
Vladimír Čunát's avatar
Vladimír Čunát committed
359
		knot_pkt_free(query);
Grigorii Demidov's avatar
Grigorii Demidov committed
360 361
		return -1;
	}
362 363
	knot_pkt_put_question(answer, dname, rrclass, rrtype);
	knot_pkt_begin(answer, KNOT_ANSWER);
Grigorii Demidov's avatar
Grigorii Demidov committed
364

365 366 367 368
	struct kr_qflags options;
	memset(&options, 0, sizeof(options));
	options.DNSSEC_WANT = true;
	options.NO_MINIMIZE = true;
Grigorii Demidov's avatar
Grigorii Demidov committed
369

370 371
	/* This call creates internal structures which necessary for
	 * resolving - qr_task & request_ctx. */
Grigorii Demidov's avatar
Grigorii Demidov committed
372 373
	struct qr_task *task = worker_resolve_start(worker, query, options);
	if (!task) {
Vladimír Čunát's avatar
Vladimír Čunát committed
374 375
		knot_pkt_free(query);
		knot_pkt_free(answer);
Grigorii Demidov's avatar
Grigorii Demidov committed
376 377 378
		return -1;
	}

379 380
	/* Push query to the request resolve plan.
	 * Actually query will never been sent to upstream. */
Grigorii Demidov's avatar
Grigorii Demidov committed
381 382 383 384 385 386
	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);
	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);
387
	uint32_t msgid = knot_wire_get_id(query->wire);
Grigorii Demidov's avatar
Grigorii Demidov committed
388 389

	qry->id = msgid;
390 391 392

	/* Prepare zonecut. It must have all the necessary requisites for
	 * successful validation - matched zone name & keys & trust-anchors. */
Grigorii Demidov's avatar
Grigorii Demidov committed
393 394 395 396 397 398 399 400
	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;
	}

401 402
	/* Since "pseudo" query asks for NS for subzone,
	 * "pseudo" answer must simulate referral. */
Grigorii Demidov's avatar
Grigorii Demidov committed
403 404 405 406
	if (is_referral) {
		knot_pkt_begin(answer, KNOT_AUTHORITY);
	}

407 408
	/* Put target rrset to ANSWER\AUTHORIRY as well as corresponding RRSIG */
	int err = zi_rrset_put(z_import, answer, rr);
Grigorii Demidov's avatar
Grigorii Demidov committed
409 410 411 412 413 414 415 416
	if (err != 0) {
		goto cleanup;
	}

	if (!is_referral) {
		knot_wire_set_aa(answer->wire);
	} else {
		/* Type is KNOT_RRTYPE_NS and owner is not equal to origin.
417 418
		 * It will be "referral" answer and must contain delegation. */
		err = zi_put_delegation(z_import, answer, rr);
Grigorii Demidov's avatar
Grigorii Demidov committed
419 420 421 422 423 424 425 426 427
		if (err < 0) {
			goto cleanup;
		}
	}

	knot_pkt_begin(answer, KNOT_ADDITIONAL);

	if (rrtype == KNOT_RRTYPE_NS) {
		/* Try to find glue addresses. */
428 429 430
		err = zi_put_glue(z_import, answer, rr);
		if (err < 0) {
			goto cleanup;
Grigorii Demidov's avatar
Grigorii Demidov committed
431 432 433 434 435
		}
	}

	knot_wire_set_id(answer->wire, msgid);
	answer->parsed = answer->size;
436 437 438 439
	err = knot_pkt_parse(answer, 0);
	if (err != KNOT_EOK) {
		goto cleanup;
	}
Grigorii Demidov's avatar
Grigorii Demidov committed
440 441 442 443 444 445 446 447

	/* 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:

Vladimír Čunát's avatar
Vladimír Čunát committed
448 449
	knot_pkt_free(query);
	knot_pkt_free(answer);
Grigorii Demidov's avatar
Grigorii Demidov committed
450 451 452 453 454 455 456 457 458 459
	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;

460
	int ret = array_push_mm(z_import->rrset_sorted, v, kr_memreserve, &z_import->pool);
Grigorii Demidov's avatar
Grigorii Demidov committed
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478

	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. */
479 480
	KR_DNAME_GET_STR(zone_name_str, z_import->origin);

Grigorii Demidov's avatar
Grigorii Demidov committed
481 482 483 484 485 486 487
	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;
	}

488
	if (z_import->rrset_sorted.len <= 0) {
Grigorii Demidov's avatar
Grigorii Demidov committed
489 490 491 492 493 494 495
		VERBOSE_MSG(NULL, "zone is empty\n");
		goto finish;
	}

	/* 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. */
496 497 498
	char key[KR_RRKEY_LEN];
	int err = kr_rrkey(key, KNOT_CLASS_IN, z_import->origin,
			   KNOT_RRTYPE_DNSKEY, KNOT_RRTYPE_DNSKEY);
Grigorii Demidov's avatar
Grigorii Demidov committed
499 500 501 502 503
	if (err <= 0) {
		failed = 1;
		goto finish;
	}

504 505
	knot_rrset_t *rr_key = map_get(&z_import->rrset_indexed, key);
	if (!rr_key) {
Grigorii Demidov's avatar
Grigorii Demidov committed
506 507 508 509 510
		/* 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;
	}
511
	z_import->key = rr_key;
Grigorii Demidov's avatar
Grigorii Demidov committed
512 513 514 515 516 517 518

	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. */
519 520
	KR_DNAME_GET_STR(kname_str, rr_key->owner);
	KR_RRTYPE_GET_STR(ktype_str, rr_key->type);
521

522 523
	VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
		    kname_str, ktype_str);
Grigorii Demidov's avatar
Grigorii Demidov committed
524

525
	int res = zi_rrset_import(z_import, rr_key);
Grigorii Demidov's avatar
Grigorii Demidov committed
526 527
	if (res != 0) {
		VERBOSE_MSG(NULL, "import failed: qname: '%s' type: '%s'\n",
528
			    kname_str, ktype_str);
Grigorii Demidov's avatar
Grigorii Demidov committed
529 530 531 532 533
		failed = 1;
		goto finish;
	}

	/* Import all NS records */
534 535
	for (size_t i = 0; i < z_import->rrset_sorted.len; ++i) {
		knot_rrset_t *rr = z_import->rrset_sorted.at[i];
Grigorii Demidov's avatar
Grigorii Demidov committed
536 537 538 539 540

		if (rr->type != KNOT_RRTYPE_NS) {
			continue;
		}

541
		KR_DNAME_GET_STR(name_str, rr->owner);
542
		KR_RRTYPE_GET_STR(type_str, rr->type);
543 544 545 546
		VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
			    name_str, type_str);
		int ret = zi_rrset_import(z_import, rr);
		if (ret == 0) {
Grigorii Demidov's avatar
Grigorii Demidov committed
547 548
			++ns_imported;
		} else {
549 550
			VERBOSE_MSG(NULL, "import failed: name: '%s' type: '%s'\n",
				    name_str, type_str);
Grigorii Demidov's avatar
Grigorii Demidov committed
551 552
			++failed;
		}
553
		z_import->rrset_sorted.at[i] = NULL;
Grigorii Demidov's avatar
Grigorii Demidov committed
554 555 556 557
	}

	/* NS records have been imported as well as relative DS, NSEC* and glue.
	 * Now import what's left. */
558
	for (size_t i = 0; i < z_import->rrset_sorted.len; ++i) {
Grigorii Demidov's avatar
Grigorii Demidov committed
559

560
		knot_rrset_t *rr = z_import->rrset_sorted.at[i];
Grigorii Demidov's avatar
Grigorii Demidov committed
561 562 563 564 565 566 567 568 569 570 571 572
		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;
		}

573
		KR_DNAME_GET_STR(name_str, rr->owner);
574
		KR_RRTYPE_GET_STR(type_str, rr->type);
575 576
		VERBOSE_MSG(NULL, "importing: name: '%s' type: '%s'\n",
			    name_str, type_str);
Grigorii Demidov's avatar
Grigorii Demidov committed
577 578 579 580
		res = zi_rrset_import(z_import, rr);
		if (res == 0) {
			++other_imported;
		} else {
581 582
			VERBOSE_MSG(NULL, "import failed: name: '%s' type: '%s'\n",
				    name_str, type_str);
Grigorii Demidov's avatar
Grigorii Demidov committed
583 584 585 586 587 588 589
			++failed;
		}
	}

	uint64_t elapsed = kr_now() - z_import->start_timestamp;
	elapsed = elapsed > UINT_MAX ? UINT_MAX : elapsed;

590 591
	VERBOSE_MSG(NULL, "finished in %"PRIu64" ms; zone: `%s`; ns: %zd"
		    "; other: %zd; failed: %zd\n",
Grigorii Demidov's avatar
Grigorii Demidov committed
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
		    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, ...); */
623 624
		kr_log_error("[zscanner] line %"PRIu64": rdata is too long\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
625 626 627 628
		return -1;
	}

	if (knot_dname_size(s->r_owner) != strlen((const char *)(s->r_owner)) + 1) {
629 630 631
		kr_log_error("[zscanner] line %"PRIu64
				": owner name contains zero byte, skip\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
632 633 634 635 636 637
		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,
638
					      s->r_ttl, &z_import->pool);
Grigorii Demidov's avatar
Grigorii Demidov committed
639
	if (!new_rr) {
640 641
		kr_log_error("[zscanner] line %"PRIu64": error creating rrset\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
642 643
		return -1;
	}
644
	int res = knot_rrset_add_rdata(new_rr, s->r_data, s->r_data_length,
645
				       &z_import->pool);
646
	if (res != KNOT_EOK) {
647 648
		kr_log_error("[zscanner] line %"PRIu64": error adding rdata to rrset\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
649 650 651
		return -1;
	}

652 653 654
	/* Records in zone file may not be grouped by name and RR type.
	 * Use map to create search key and
	 * avoid ineffective searches across all the imported records. */
655
	char key[KR_RRKEY_LEN];
Grigorii Demidov's avatar
Grigorii Demidov committed
656 657
	uint16_t additional_key_field = kr_rrset_type_maysig(new_rr);

658
	res = kr_rrkey(key, new_rr->rclass, new_rr->owner, new_rr->type,
659
		       additional_key_field);
660
	if (res <= 0) {
661 662
		kr_log_error("[zscanner] line %"PRIu64": error constructing rrkey\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
663 664 665
		return -1;
	}

666
	knot_rrset_t *saved_rr = map_get(&z_import->rrset_indexed, key);
Grigorii Demidov's avatar
Grigorii Demidov committed
667
	if (saved_rr) {
668
		res = knot_rdataset_merge(&saved_rr->rrs, &new_rr->rrs,
Grigorii Demidov's avatar
Grigorii Demidov committed
669 670
					  &z_import->pool);
	} else {
671
		res = map_set(&z_import->rrset_indexed, key, new_rr);
Grigorii Demidov's avatar
Grigorii Demidov committed
672
	}
673
	if (res != 0) {
674 675
		kr_log_error("[zscanner] line %"PRIu64": error saving parsed rrset\n",
				s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
		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)) {
696 697
				kr_log_error("[zscanner] line: %"PRIu64
					     ": zone origin changed unexpectedly\n",
Grigorii Demidov's avatar
Grigorii Demidov committed
698 699 700 701 702
					     s->line_counter);
				return -1;
			}
			break;
		case ZS_STATE_ERROR:
703 704 705 706
			kr_log_error("[zscanner] line: %"PRIu64
				     ": parse error; code: %i ('%s')\n",
				     s->line_counter, s->error.code,
				     zs_strerror(s->error.code));
Grigorii Demidov's avatar
Grigorii Demidov committed
707 708
			return -1;
		case ZS_STATE_INCLUDE:
709 710
			kr_log_error("[zscanner] line: %"PRIu64
				     ": INCLUDE is not supported\n",
711
				     s->line_counter);
Grigorii Demidov's avatar
Grigorii Demidov committed
712
			return -1;
713 714
		case ZS_STATE_EOF:
		case ZS_STATE_STOP:
Grigorii Demidov's avatar
Grigorii Demidov committed
715
			return (s->error.counter == 0) ? 0 : -1;
716
		default:
717 718
			kr_log_error("[zscanner] line: %"PRIu64
				     ": unexpected parse state: %i\n",
719 720
				     s->line_counter, s->state);
			return -1;
Grigorii Demidov's avatar
Grigorii Demidov committed
721 722 723 724 725 726 727 728 729 730
		}
	}

	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)
{
731 732 733
	assert (z_import != NULL && "[zimport] empty <z_import> parameter");
	assert (z_import->worker != NULL && "[zimport] invalid <z_import> parameter\n");
	assert (zone_file != NULL && "[zimport] empty <zone_file> parameter\n");
Grigorii Demidov's avatar
Grigorii Demidov committed
734 735 736

	zs_scanner_t *s = malloc(sizeof(zs_scanner_t));
	if (s == NULL) {
737
		kr_log_error("[zscanner] error creating instance of zone scanner (malloc() fails)\n");
Grigorii Demidov's avatar
Grigorii Demidov committed
738 739 740
		return -1;
	}

741 742 743 744 745 746
	/* zs_init(), zs_set_input_file(), zs_set_processing() returns -1 in case of error,
	 * so don't print error code as it meaningless. */
	int res = zs_init(s, origin, rclass, ttl);
	if (res != 0) {
		kr_log_error("[zscanner] error initializing zone scanner instance, error: %i (%s)\n",
			     s->error.code, zs_strerror(s->error.code));
Petr Špaček's avatar
Petr Špaček committed
747
		free(s);
Grigorii Demidov's avatar
Grigorii Demidov committed
748 749 750
		return -1;
	}

751 752 753 754
	res = zs_set_input_file(s, zone_file);
	if (res != 0) {
		kr_log_error("[zscanner] error opening zone file `%s`, error: %i (%s)\n",
			     zone_file, s->error.code, zs_strerror(s->error.code));
Petr Špaček's avatar
Petr Špaček committed
755 756
		zs_deinit(s);
		free(s);
Grigorii Demidov's avatar
Grigorii Demidov committed
757 758 759
		return -1;
	}

760 761 762
	/* Don't set processing and error callbacks as we don't use automatic parsing.
	 * Parsing as well error processing will be performed in zi_state_parsing().
	 * Store pointer to zone import context for further use. */
Grigorii Demidov's avatar
Grigorii Demidov committed
763
	if (zs_set_processing(s, NULL, NULL, (void *)z_import) != 0) {
Petr Špaček's avatar
Petr Špaček committed
764 765 766
		kr_log_error("[zscanner] zs_set_processing() failed for zone file `%s`, "
				"error: %i (%s)\n",
				zone_file, s->error.code, zs_strerror(s->error.code));
Grigorii Demidov's avatar
Grigorii Demidov committed
767 768 769 770 771 772 773 774 775 776 777 778 779
		zs_deinit(s);
		free(s);
		return -1;
	}

	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);
780 781 782 783 784 785 786 787
		/* 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) {
			z_import->ta = rr;
		} else {
			/* For now - fail.
			 * TODO - query DS and continue after answer had been obtained. */
788
			KR_DNAME_GET_STR(zone_name_str, z_import->origin);
789 790 791
			kr_log_error("[zimport] no TA found for `%s`, fail\n", zone_name_str);
			ret = 1;
		}
Grigorii Demidov's avatar
Grigorii Demidov committed
792 793 794 795 796 797 798 799 800 801 802 803
		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;
	}

804
	VERBOSE_MSG(NULL, "[zscanner] finished in %"PRIu64" ms; zone file `%s`\n",
Grigorii Demidov's avatar
Grigorii Demidov committed
805
			    elapsed, zone_file);
806
	map_walk(&z_import->rrset_indexed, zi_mapwalk_preprocess, z_import);
Grigorii Demidov's avatar
Grigorii Demidov committed
807

808
	/* Zone have been parsed already, so start the import. */
Grigorii Demidov's avatar
Grigorii Demidov committed
809 810 811 812 813 814 815 816 817 818
	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;
}