apply.c 13.4 KB
Newer Older
Daniel Salzman's avatar
Daniel Salzman committed
1
/*  Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
Lubos Slovak's avatar
Lubos Slovak committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

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

17 18
#include <assert.h>

19
#include "knot/common/log.h"
Daniel Salzman's avatar
Daniel Salzman committed
20
#include "knot/updates/apply.h"
21
#include "libknot/libknot.h"
22
#include "contrib/macros.h"
23
#include "contrib/mempattern.h"
24

25
/* --------------------------- Update cleanup ------------------------------- */
26

27 28 29 30 31 32
/*!
 * \brief Post update cleanup: frees data that are in the tree that will not
 *        be used (old tree if success, new tree if failure).
 *          Freed data:
 *           - actual data inside knot_rrs_t. (the rest is part of the node)
 */
33
static void rrs_list_clear(list_t *l, knot_mm_t *mm)
34
{
35 36 37
	ptrnode_t *n;
	node_t *nxt;
	WALK_LIST_DELSAFE(n, nxt, *l) {
38 39
		mm_free(mm, (void *)n->d);
		mm_free(mm, n);
40
	};
41
}
Jan Včelák's avatar
Jan Včelák committed
42

43
/*! \brief Frees additional data from single node */
Jan Kadlec's avatar
Jan Kadlec committed
44
static int free_additional(zone_node_t **node, void *data)
45
{
46
	UNUSED(data);
Jan Kadlec's avatar
Jan Kadlec committed
47
	if ((*node)->flags & NODE_FLAGS_NONAUTH) {
48 49 50 51
		// non-auth nodes have no additionals.
		return KNOT_EOK;
	}

52 53 54 55 56 57 58 59 60 61 62
	for (uint16_t i = 0; i < (*node)->rrset_count; ++i) {
		struct rr_data *data = &(*node)->rrs[i];
		if (data->additional) {
			free(data->additional);
			data->additional = NULL;
		}
	}

	return KNOT_EOK;
}

63
/* -------------------- Changeset application helpers ----------------------- */
64

65 66
/*! \brief Replaces rdataset of given type with a copy. */
static int replace_rdataset_with_copy(zone_node_t *node, uint16_t type)
67
{
68
	// Find data to copy.
69 70 71 72
	struct rr_data *data = NULL;
	for (uint16_t i = 0; i < node->rrset_count; ++i) {
		if (node->rrs[i].type == type) {
			data = &node->rrs[i];
73
			break;
74
		}
75
	}
76
	assert(data);
77

78
	// Create new data.
79 80
	knot_rdataset_t *rrs = &data->rrs;
	void *copy = malloc(knot_rdataset_size(rrs));
81 82
	if (copy == NULL) {
		return KNOT_ENOMEM;
83
	}
Jan Včelák's avatar
Jan Včelák committed
84

85
	memcpy(copy, rrs->data, knot_rdataset_size(rrs));
Jan Včelák's avatar
Jan Včelák committed
86

87
	// Store new data into node RRS.
88
	rrs->data = copy;
Jan Včelák's avatar
Jan Včelák committed
89

Lubos Slovak's avatar
Lubos Slovak committed
90
	return KNOT_EOK;
91 92
}

93
/*! \brief Frees RR dataset. For use when a copy was made. */
94
static void clear_new_rrs(zone_node_t *node, uint16_t type)
95
{
96
	knot_rdataset_t *new_rrs = node_rdataset(node, type);
97
	if (new_rrs) {
98
		knot_rdataset_clear(new_rrs, NULL);
99
	}
100
}
Jan Včelák's avatar
Jan Včelák committed
101

102
/*! \brief Stores RR data for update cleanup. */
103
static int add_old_data(apply_ctx_t *ctx, knot_rdata_t *old_data)
104
{
105
	if (ptrlist_add(&ctx->old_data, old_data, NULL) == NULL) {
106 107 108 109 110 111 112
		return KNOT_ENOMEM;
	}

	return KNOT_EOK;
}

/*! \brief Stores RR data for update rollback. */
113
static int add_new_data(apply_ctx_t *ctx, knot_rdata_t *new_data)
114
{
115
	if (ptrlist_add(&ctx->new_data, new_data, NULL) == NULL) {
116 117 118 119 120 121 122
		return KNOT_ENOMEM;
	}

	return KNOT_EOK;
}

/*! \brief Returns true if given RR is present in node and can be removed. */
123
static bool can_remove(const zone_node_t *node, const knot_rrset_t *rr)
124 125
{
	if (node == NULL) {
126
		// Node does not exist, cannot remove anything.
127
		return false;
128
	}
129
	const knot_rdataset_t *node_rrs = node_rdataset(node, rr->type);
130
	if (node_rrs == NULL) {
131
		// Node does not have this type at all.
132
		return false;
133
	}
Jan Včelák's avatar
Jan Včelák committed
134

135
	const bool compare_ttls = false;
136
	for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
137 138
		knot_rdata_t *rr_cmp = knot_rdataset_at(&rr->rrs, i);
		if (knot_rdataset_member(node_rrs, rr_cmp, compare_ttls)) {
139
			// At least one RR matches.
140
			return true;
141 142
		}
	}
Jan Včelák's avatar
Jan Včelák committed
143

144
	// Node does have the type, but no RRs match.
145
	return false;
146 147
}

148 149
/*! \brief Removes all RRs from changeset from zone contents. */
static int apply_remove(apply_ctx_t *ctx, changeset_t *chset)
150
{
151 152
	changeset_iter_t itt;
	changeset_iter_rem(&itt, chset, false);
153

154 155 156
	knot_rrset_t rr = changeset_iter_next(&itt);
	while (!knot_rrset_empty(&rr)) {
		int ret = apply_remove_rr(ctx, &rr);
157
		if (ret != KNOT_EOK) {
158
			changeset_iter_clear(&itt);
159 160
			return ret;
		}
161 162

		rr = changeset_iter_next(&itt);
163
	}
164
	changeset_iter_clear(&itt);
165

166
	return KNOT_EOK;
167 168
}

169 170
/*! \brief Adds all RRs from changeset into zone contents. */
static int apply_add(apply_ctx_t *ctx, changeset_t *chset)
171
{
172
	changeset_iter_t itt;
173
	changeset_iter_add(&itt, chset, false);
174

175
	knot_rrset_t rr = changeset_iter_next(&itt);
176 177
	while(!knot_rrset_empty(&rr)) {
		int ret = apply_add_rr(ctx, &rr);
178
		if (ret != KNOT_EOK) {
179
			changeset_iter_clear(&itt);
180 181
			return ret;
		}
182
		rr = changeset_iter_next(&itt);
183
	}
184
	changeset_iter_clear(&itt);
Jan Včelák's avatar
Jan Včelák committed
185

186
	return KNOT_EOK;
187 188
}

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
/*! \brief Apply single change to zone contents structure. */
static int apply_single(apply_ctx_t *ctx, changeset_t *chset)
{
	/*
	 * Applies one changeset to the zone. Checks if the changeset may be
	 * applied (i.e. the origin SOA (soa_from) has the same serial as
	 * SOA in the zone apex.
	 */

	zone_contents_t *contents = ctx->contents;

	// check if serial matches
	const knot_rdataset_t *soa = node_rdataset(contents->apex, KNOT_RRTYPE_SOA);
	if (soa == NULL || knot_soa_serial(soa) != knot_soa_serial(&chset->soa_from->rrs)) {
		return KNOT_EINVAL;
	}

	int ret = apply_remove(ctx, chset);
	if (ret != KNOT_EOK) {
		return ret;
	}

	ret = apply_add(ctx, chset);
	if (ret != KNOT_EOK) {
		return ret;
	}

	return apply_replace_soa(ctx, chset);
}

/* ------------------------------- API -------------------------------------- */

void apply_init_ctx(apply_ctx_t *ctx, zone_contents_t *contents, uint32_t flags)
{
	assert(ctx);

	ctx->contents = contents;

	init_list(&ctx->old_data);
	init_list(&ctx->new_data);

	ctx->flags = flags;
}

int apply_prepare_zone_copy(zone_contents_t *old_contents,
Daniel Salzman's avatar
Daniel Salzman committed
234
                            zone_contents_t **new_contents)
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
	if (old_contents == NULL || new_contents == NULL) {
		return KNOT_EINVAL;
	}

	/*
	 * Create a shallow copy of the zone, so that the structures may be
	 * updated.
	 *
	 * This will create new zone contents structures (normal nodes' tree,
	 * NSEC3 tree), and copy all nodes.
	 * The data in the nodes (RRSets) remain the same though.
	 */
	zone_contents_t *contents_copy = NULL;
	int ret = zone_contents_shallow_copy(old_contents, &contents_copy);
	if (ret != KNOT_EOK) {
		return ret;
	}

	*new_contents = contents_copy;

	return KNOT_EOK;
}

int apply_add_rr(apply_ctx_t *ctx, const knot_rrset_t *rr)
{
	zone_contents_t *contents = ctx->contents;

263
	// Get or create node with this owner
264
	zone_node_t *node = zone_contents_get_node_for_rr(contents, rr);
265 266 267 268
	if (node == NULL) {
		return KNOT_ENOMEM;
	}

269
	knot_rrset_t changed_rrset = node_rrset(node, rr->type);
270 271
	if (!knot_rrset_empty(&changed_rrset)) {
		// Modifying existing RRSet.
272
		knot_rdata_t *old_data = changed_rrset.rrs.data;
273
		int ret = replace_rdataset_with_copy(node, rr->type);
274 275
		if (ret != KNOT_EOK) {
			return ret;
276 277
		}

Jan Kadlec's avatar
Jan Kadlec committed
278
		// Store old RRS for cleanup.
279
		ret = add_old_data(ctx, old_data);
280
		if (ret != KNOT_EOK) {
281
			clear_new_rrs(node, rr->type);
282
			return ret;
283
		}
284
	}
285

286
	// Insert new RR to RRSet, data will be copied.
287
	int ret = node_add_rrset(node, rr, NULL);
288 289 290
	if (ret == KNOT_EOK || ret == KNOT_ETTL) {
		// RR added, store for possible rollback.
		knot_rdataset_t *rrs = node_rdataset(node, rr->type);
291
		int data_ret = add_new_data(ctx, rrs->data);
292 293 294
		if (data_ret != KNOT_EOK) {
			knot_rdataset_clear(rrs, NULL);
			return data_ret;
295 296
		}

297
		if (ret == KNOT_ETTL) {
298
			log_zone_notice(contents->apex->owner,
299 300
			                "rrset (type %u) TTL mismatch, updated to %u",
			                rr->type, knot_rrset_ttl(rr));
301
			return KNOT_EOK;
302
		}
303 304
	}

305
	return ret;
306 307
}

308
int apply_remove_rr(apply_ctx_t *ctx, const knot_rrset_t *rr)
309
{
310
	zone_contents_t *contents = ctx->contents;
311

312 313 314 315 316 317 318
	// Find node for this owner
	zone_node_t *node = zone_contents_find_node_for_rr(contents, rr);
	if (!can_remove(node, rr)) {
		// Cannot be removed, either no node or nonexistent RR
		if (ctx->flags & APPLY_STRICT) {
			// Don't ignore missing RR if strict. Required for IXFR.
			return KNOT_ENORECORD;
319
		}
320
		return KNOT_EOK;
321
	}
322

323
	zone_tree_t *tree = knot_rrset_is_nsec3rel(rr) ?
Daniel Salzman's avatar
Daniel Salzman committed
324
	                    contents->nsec3_nodes : contents->nodes;
325

326 327 328
	knot_rrset_t removed_rrset = node_rrset(node, rr->type);
	knot_rdata_t *old_data = removed_rrset.rrs.data;
	int ret = replace_rdataset_with_copy(node, rr->type);
329 330
	if (ret != KNOT_EOK) {
		return ret;
331 332
	}

333 334
	// Store old data for cleanup.
	ret = add_old_data(ctx, old_data);
335
	if (ret != KNOT_EOK) {
336
		clear_new_rrs(node, rr->type);
337 338 339
		return ret;
	}

340 341 342
	knot_rdataset_t *changed_rrs = node_rdataset(node, rr->type);
	// Subtract changeset RRS from node RRS.
	ret = knot_rdataset_subtract(changed_rrs, &rr->rrs, NULL);
343
	if (ret != KNOT_EOK) {
344
		clear_new_rrs(node, rr->type);
345 346 347
		return ret;
	}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
	if (changed_rrs->rr_count > 0) {
		// Subtraction left some data in RRSet, store it for rollback.
		ret = add_new_data(ctx, changed_rrs->data);
		if (ret != KNOT_EOK) {
			knot_rdataset_clear(changed_rrs, NULL);
			return ret;
		}
	} else {
		// RRSet is empty now, remove it from node, all data freed.
		node_remove_rdataset(node, rr->type);
		// If node is empty now, delete it from zone tree.
		if (node->rrset_count == 0 && node != contents->apex) {
			zone_tree_delete_empty_node(tree, node);
		}
	}
363

364 365
	return KNOT_EOK;
}
Lubos Slovak's avatar
Lubos Slovak committed
366

367
int apply_replace_soa(apply_ctx_t *ctx, changeset_t *chset)
368
{
369
	zone_contents_t *contents = ctx->contents;
370

371 372
	assert(chset->soa_from && chset->soa_to);
	int ret = apply_remove_rr(ctx, chset->soa_from);
373 374 375 376
	if (ret != KNOT_EOK) {
		return ret;
	}

377 378 379 380
	// Check for SOA with proper serial but different rdata.
	if (node_rrtype_exists(contents->apex, KNOT_RRTYPE_SOA)) {
		return KNOT_EINVAL;
	}
Jan Včelák's avatar
Jan Včelák committed
381

382
	return apply_add_rr(ctx, chset->soa_to);
383
}
384

385
int apply_prepare_to_sign(apply_ctx_t *ctx)
386
{
387
	return zone_contents_adjust_pointers(ctx->contents);
388 389
}

Daniel Salzman's avatar
Daniel Salzman committed
390 391
int apply_changesets(apply_ctx_t *ctx, zone_t *zone, list_t *chsets,
                     zone_contents_t **new_contents)
392
{
Daniel Salzman's avatar
Daniel Salzman committed
393 394
	if (ctx == NULL || zone == NULL || chsets == NULL ||
	    EMPTY_LIST(*chsets) || new_contents == NULL) {
395 396 397
		return KNOT_EINVAL;
	}

398
	zone_contents_t *old_contents = zone->contents;
399 400 401 402
	if (!old_contents) {
		return KNOT_EINVAL;
	}

403
	zone_contents_t *contents_copy = NULL;
404
	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
405 406 407 408
	if (ret != KNOT_EOK) {
		return ret;
	}

409 410
	ctx->contents = contents_copy;

411
	changeset_t *set = NULL;
412
	WALK_LIST(set, *chsets) {
413
		ret = apply_single(ctx, set);
Jan Kadlec's avatar
Jan Kadlec committed
414
		if (ret != KNOT_EOK) {
415
			update_rollback(ctx);
416
			update_free_zone(&ctx->contents);
417 418 419
			return ret;
		}
	}
420

421
	assert(contents_copy->apex != NULL);
422

423
	ret = zone_contents_adjust_full(contents_copy);
424
	if (ret != KNOT_EOK) {
425
		update_rollback(ctx);
426
		update_free_zone(&ctx->contents);
427 428
		return ret;
	}
Jan Včelák's avatar
Jan Včelák committed
429

430 431 432 433 434
	*new_contents = contents_copy;

	return KNOT_EOK;
}

Daniel Salzman's avatar
Daniel Salzman committed
435 436
int apply_changeset(apply_ctx_t *ctx, zone_t *zone, changeset_t *ch,
                    zone_contents_t **new_contents)
437
{
Daniel Salzman's avatar
Daniel Salzman committed
438
	if (ctx == NULL || zone == NULL || ch == NULL || new_contents == NULL) {
439 440 441 442 443 444 445 446 447
		return KNOT_EINVAL;
	}

	zone_contents_t *old_contents = zone->contents;
	if (!old_contents) {
		return KNOT_EINVAL;
	}

	zone_contents_t *contents_copy = NULL;
448
	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
449 450 451
	if (ret != KNOT_EOK) {
		return ret;
	}
452

453 454
	ctx->contents = contents_copy;

Daniel Salzman's avatar
Daniel Salzman committed
455
	ret = apply_single(ctx, ch);
456
	if (ret != KNOT_EOK) {
457
		update_rollback(ctx);
458
		update_free_zone(&ctx->contents);
459 460
		return ret;
	}
461

462
	ret = zone_contents_adjust_full(contents_copy);
463
	if (ret != KNOT_EOK) {
464
		update_rollback(ctx);
465
		update_free_zone(&ctx->contents);
466 467
		return ret;
	}
468

469
	*new_contents = contents_copy;
470

471 472 473
	return KNOT_EOK;
}

474
int apply_changesets_directly(apply_ctx_t *ctx, list_t *chsets)
475
{
476
	if (ctx == NULL || ctx->contents == NULL || chsets == NULL) {
477 478 479
		return KNOT_EINVAL;
	}

480
	changeset_t *set = NULL;
481
	WALK_LIST(set, *chsets) {
482
		int ret = apply_single(ctx, set);
483
		if (ret != KNOT_EOK) {
484
			update_rollback(ctx);
485 486 487 488
			return ret;
		}
	}

489
	int ret = zone_contents_adjust_full(ctx->contents);
490
	if (ret != KNOT_EOK) {
491
		update_rollback(ctx);
492
	}
493

494
	return ret;
495 496
}

497
int apply_changeset_directly(apply_ctx_t *ctx, changeset_t *ch)
498
{
499
	if (ctx == NULL || ctx->contents == NULL || ch == NULL) {
500 501
		return KNOT_EINVAL;
	}
502

503
	int ret = apply_single(ctx, ch);
504
	if (ret != KNOT_EOK) {
505
		update_rollback(ctx);
506 507
		return ret;
	}
508

509
	zone_contents_adjust_full(ctx->contents);
510
	if (ret != KNOT_EOK) {
511
		update_rollback(ctx);
512 513
		return ret;
	}
514

515 516
	return KNOT_EOK;
}
517

518 519 520 521 522
int apply_finalize(apply_ctx_t *ctx)
{
	return zone_contents_adjust_full(ctx->contents);
}

523
void update_cleanup(apply_ctx_t *ctx)
524
{
Daniel Salzman's avatar
Daniel Salzman committed
525 526
	if (ctx == NULL) {
		return;
527
	}
Daniel Salzman's avatar
Daniel Salzman committed
528 529 530 531 532 533 534

	// Delete old RR data
	rrs_list_clear(&ctx->old_data, NULL);
	init_list(&ctx->old_data);
	// Keep new RR data
	ptrlist_free(&ctx->new_data, NULL);
	init_list(&ctx->new_data);
535 536
}

537
void update_rollback(apply_ctx_t *ctx)
538
{
Daniel Salzman's avatar
Daniel Salzman committed
539 540
	if (ctx == NULL) {
		return;
541
	}
Daniel Salzman's avatar
Daniel Salzman committed
542 543 544 545 546 547 548

	// Delete new RR data
	rrs_list_clear(&ctx->new_data, NULL);
	init_list(&ctx->new_data);
	// Keep old RR data
	ptrlist_free(&ctx->old_data, NULL);
	init_list(&ctx->old_data);
549 550
}

551
void update_free_zone(zone_contents_t **contents)
552
{
Daniel Salzman's avatar
Daniel Salzman committed
553 554 555
	if (contents == NULL || *contents == NULL) {
		return;
	}
556

Daniel Salzman's avatar
Daniel Salzman committed
557 558 559
	zone_tree_apply((*contents)->nodes, free_additional, NULL);
	zone_tree_deep_free(&(*contents)->nodes);
	zone_tree_deep_free(&(*contents)->nsec3_nodes);
560

Daniel Salzman's avatar
Daniel Salzman committed
561 562 563 564
	dnssec_nsec3_params_free(&(*contents)->nsec3_params);

	free(*contents);
	*contents = NULL;
565
}