apply.c 13.2 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"
Lubos Slovak's avatar
Lubos Slovak committed
24

Jan Kadlec's avatar
Jan Kadlec committed
25
/* --------------------------- Update cleanup ------------------------------- */
26

Jan Kadlec's avatar
Jan Kadlec committed
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
{
Jan Kadlec's avatar
Jan Kadlec committed
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

Jan Kadlec's avatar
Jan Kadlec committed
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
	for (uint16_t i = 0; i < (*node)->rrset_count; ++i) {
		struct rr_data *data = &(*node)->rrs[i];
54 55
		additional_clear(data->additional);
		data->additional = NULL;
56 57 58 59 60
	}

	return KNOT_EOK;
}

Jan Kadlec's avatar
Jan Kadlec committed
61
/* -------------------- Changeset application helpers ----------------------- */
62

Jan Kadlec's avatar
Jan Kadlec committed
63 64
/*! \brief Replaces rdataset of given type with a copy. */
static int replace_rdataset_with_copy(zone_node_t *node, uint16_t type)
65
{
66
	// Find data to copy.
67 68 69 70
	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];
71
			break;
72
		}
73
	}
74
	assert(data);
75

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

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

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

Lubos Slovak's avatar
Lubos Slovak committed
88
	return KNOT_EOK;
89 90
}

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

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

	return KNOT_EOK;
}

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

	return KNOT_EOK;
}

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

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

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

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

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

		rr = changeset_iter_next(&itt);
161
	}
162
	changeset_iter_clear(&itt);
163

164
	return KNOT_EOK;
165 166
}

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

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

184
	return KNOT_EOK;
185 186
}

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
/*! \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
232
                            zone_contents_t **new_contents)
Lubos Slovak's avatar
Lubos Slovak committed
233
{
234 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
	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;

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

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

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

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

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

303
	return ret;
304 305
}

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

310 311 312 313 314 315 316
	// 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;
317
		}
318
		return KNOT_EOK;
319
	}
320

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

324 325 326
	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);
327 328
	if (ret != KNOT_EOK) {
		return ret;
329 330
	}

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

338 339 340
	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);
341
	if (ret != KNOT_EOK) {
342
		clear_new_rrs(node, rr->type);
343 344 345
		return ret;
	}

346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
	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);
		}
	}
361

362 363
	return KNOT_EOK;
}
Lubos Slovak's avatar
Lubos Slovak committed
364

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

369 370 371 372
	if (!knot_dname_is_equal(chset->soa_to->owner, contents->apex->owner)) {
		return KNOT_EDENIED;
	}

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

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

384
	return apply_add_rr(ctx, chset->soa_to);
385
}
386

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

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

400
	zone_contents_t *contents_copy = NULL;
401
	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
402 403 404 405
	if (ret != KNOT_EOK) {
		return ret;
	}

406 407
	ctx->contents = contents_copy;

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

418
	assert(contents_copy->apex != NULL);
419

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

427 428 429 430 431
	*new_contents = contents_copy;

	return KNOT_EOK;
}

432 433
int apply_changeset(apply_ctx_t *ctx, zone_contents_t *old_contents,
                    changeset_t *ch, zone_contents_t **new_contents)
434
{
435
	if (ctx == NULL || old_contents == NULL || ch == NULL || new_contents == NULL) {
436 437 438 439
		return KNOT_EINVAL;
	}

	zone_contents_t *contents_copy = NULL;
440
	int ret = apply_prepare_zone_copy(old_contents, &contents_copy);
441 442 443
	if (ret != KNOT_EOK) {
		return ret;
	}
444

445 446
	ctx->contents = contents_copy;

Daniel Salzman's avatar
Daniel Salzman committed
447
	ret = apply_single(ctx, ch);
448
	if (ret != KNOT_EOK) {
449
		update_rollback(ctx);
450
		update_free_zone(&ctx->contents);
451 452
		return ret;
	}
453

454
	ret = zone_contents_adjust_full(contents_copy);
455
	if (ret != KNOT_EOK) {
456
		update_rollback(ctx);
457
		update_free_zone(&ctx->contents);
458 459
		return ret;
	}
460

461
	*new_contents = contents_copy;
462

463 464 465
	return KNOT_EOK;
}

466
int apply_changesets_directly(apply_ctx_t *ctx, list_t *chsets)
Jan Kadlec's avatar
Jan Kadlec committed
467
{
468
	if (ctx == NULL || ctx->contents == NULL || chsets == NULL) {
Jan Kadlec's avatar
Jan Kadlec committed
469 470 471
		return KNOT_EINVAL;
	}

472
	changeset_t *set = NULL;
473
	WALK_LIST(set, *chsets) {
474
		int ret = apply_single(ctx, set);
Jan Kadlec's avatar
Jan Kadlec committed
475 476 477 478 479
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

480
	return zone_contents_adjust_full(ctx->contents);
Jan Kadlec's avatar
Jan Kadlec committed
481 482
}

483
int apply_changeset_directly(apply_ctx_t *ctx, changeset_t *ch)
Jan Kadlec's avatar
Jan Kadlec committed
484
{
485
	if (ctx == NULL || ctx->contents == NULL || ch == NULL) {
486 487
		return KNOT_EINVAL;
	}
488

489
	int ret = apply_single(ctx, ch);
490
	if (ret != KNOT_EOK) {
491
		update_rollback(ctx);
492 493
		return ret;
	}
494

Dominik Taborsky's avatar
Dominik Taborsky committed
495
	ret = zone_contents_adjust_full(ctx->contents);
496
	if (ret != KNOT_EOK) {
497
		update_rollback(ctx);
498 499
		return ret;
	}
500

501 502
	return KNOT_EOK;
}
Jan Kadlec's avatar
Jan Kadlec committed
503

504 505 506 507 508
int apply_finalize(apply_ctx_t *ctx)
{
	return zone_contents_adjust_full(ctx->contents);
}

509
void update_cleanup(apply_ctx_t *ctx)
510
{
Daniel Salzman's avatar
Daniel Salzman committed
511 512
	if (ctx == NULL) {
		return;
513
	}
Daniel Salzman's avatar
Daniel Salzman committed
514 515 516 517 518 519 520

	// 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);
Jan Kadlec's avatar
Jan Kadlec committed
521 522
}

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

	// 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);
Jan Kadlec's avatar
Jan Kadlec committed
535 536
}

Jan Kadlec's avatar
Jan Kadlec committed
537
void update_free_zone(zone_contents_t **contents)
Jan Kadlec's avatar
Jan Kadlec committed
538
{
Daniel Salzman's avatar
Daniel Salzman committed
539 540 541
	if (contents == NULL || *contents == NULL) {
		return;
	}
Jan Kadlec's avatar
Jan Kadlec committed
542

Daniel Salzman's avatar
Daniel Salzman committed
543 544 545
	zone_tree_apply((*contents)->nodes, free_additional, NULL);
	zone_tree_deep_free(&(*contents)->nodes);
	zone_tree_deep_free(&(*contents)->nsec3_nodes);
Jan Kadlec's avatar
Jan Kadlec committed
546

Daniel Salzman's avatar
Daniel Salzman committed
547 548 549 550
	dnssec_nsec3_params_free(&(*contents)->nsec3_params);

	free(*contents);
	*contents = NULL;
Jan Kadlec's avatar
Jan Kadlec committed
551
}