apply.c 12.1 KB
Newer Older
1
/*  Copyright (C) 2019 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

    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
14
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
Lubos Slovak's avatar
Lubos Slovak committed
15 16
 */

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 26
/*! \brief Replaces rdataset of given type with a copy. */
static int replace_rdataset_with_copy(zone_node_t *node, uint16_t type)
27
{
28 29 30 31 32
	int ret = binode_prepare_change(node, NULL);
	if (ret != KNOT_EOK) {
		return ret;
	}

33
	// Find data to copy.
34 35 36 37
	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];
38
			break;
39
		}
40
	}
41 42 43
	if (data == NULL) {
		return KNOT_EOK;
	}
44

45
	// Create new data.
46 47
	knot_rdataset_t *rrs = &data->rrs;
	void *copy = malloc(knot_rdataset_size(rrs));
48 49
	if (copy == NULL) {
		return KNOT_ENOMEM;
50
	}
Jan Včelák's avatar
Jan Včelák committed
51

52
	memcpy(copy, rrs->rdata, knot_rdataset_size(rrs));
Jan Včelák's avatar
Jan Včelák committed
53

54
	// Store new data into node RRS.
55
	rrs->rdata = copy;
Jan Včelák's avatar
Jan Včelák committed
56

Lubos Slovak's avatar
Lubos Slovak committed
57
	return KNOT_EOK;
58 59
}

60
/*! \brief Frees RR dataset. For use when a copy was made. */
61
static void clear_new_rrs(zone_node_t *node, uint16_t type)
62
{
63
	knot_rdataset_t *new_rrs = node_rdataset(node, type);
64
	if (new_rrs) {
65
		knot_rdataset_clear(new_rrs, NULL);
66
	}
67
}
Jan Včelák's avatar
Jan Včelák committed
68

69
/*! \brief Returns true if given RR is present in node and can be removed. */
70
static bool can_remove(const zone_node_t *node, const knot_rrset_t *rrset)
71 72
{
	if (node == NULL) {
73
		// Node does not exist, cannot remove anything.
74
		return false;
75
	}
76
	const knot_rdataset_t *node_rrs = node_rdataset(node, rrset->type);
77
	if (node_rrs == NULL) {
78
		// Node does not have this type at all.
79
		return false;
80
	}
Jan Včelák's avatar
Jan Včelák committed
81

82 83
	knot_rdata_t *rr_cmp = rrset->rrs.rdata;
	for (uint16_t i = 0; i < rrset->rrs.count; ++i) {
84
		if (knot_rdataset_member(node_rrs, rr_cmp)) {
85
			// At least one RR matches.
86
			return true;
87
		}
88
		rr_cmp = knot_rdataset_next(rr_cmp);
89
	}
Jan Včelák's avatar
Jan Včelák committed
90

91
	// Node does have the type, but no RRs match.
92
	return false;
93 94
}

95
/*! \brief Removes all RRs from changeset from zone contents. */
96
static int apply_remove(apply_ctx_t *ctx, const changeset_t *chset)
97
{
98
	changeset_iter_t itt;
99
	changeset_iter_rem(&itt, chset);
100

101 102 103
	knot_rrset_t rr = changeset_iter_next(&itt);
	while (!knot_rrset_empty(&rr)) {
		int ret = apply_remove_rr(ctx, &rr);
104
		if (ret != KNOT_EOK) {
105
			changeset_iter_clear(&itt);
106 107
			return ret;
		}
108 109

		rr = changeset_iter_next(&itt);
110
	}
111
	changeset_iter_clear(&itt);
112

113
	return KNOT_EOK;
114 115
}

116
/*! \brief Adds all RRs from changeset into zone contents. */
117
static int apply_add(apply_ctx_t *ctx, const changeset_t *chset)
118
{
119
	changeset_iter_t itt;
120
	changeset_iter_add(&itt, chset);
121

122
	knot_rrset_t rr = changeset_iter_next(&itt);
123 124
	while(!knot_rrset_empty(&rr)) {
		int ret = apply_add_rr(ctx, &rr);
125
		if (ret != KNOT_EOK) {
126
			changeset_iter_clear(&itt);
127 128
			return ret;
		}
129
		rr = changeset_iter_next(&itt);
130
	}
131
	changeset_iter_clear(&itt);
Jan Včelák's avatar
Jan Včelák committed
132

133
	return KNOT_EOK;
134 135
}

136
/*! \brief Apply single change to zone contents structure. */
137
static int apply_single(apply_ctx_t *ctx, const changeset_t *chset)
138 139 140 141 142 143 144 145 146 147 148
{
	/*
	 * 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);
149 150 151 152 153 154 155

	// either we are in the mode of ignoring SOA (both NULL), or we shall be strict and apply SOA later
	bool ignore_soa = (chset->soa_from == NULL && chset->soa_to == NULL);
	bool soa_mismatch = (chset->soa_from == NULL || chset->soa_to == NULL || soa == NULL ||
			     knot_soa_serial(soa->rdata) != knot_soa_serial(chset->soa_from->rrs.rdata));

	if (soa == NULL || (!ignore_soa && soa_mismatch)) {
156 157 158 159 160 161 162 163 164 165 166 167 168
		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;
	}

169
	return (ignore_soa ? KNOT_EOK : apply_replace_soa(ctx, chset));
170 171 172 173
}

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

Libor Peltan's avatar
Libor Peltan committed
174
int apply_init_ctx(apply_ctx_t *ctx, zone_contents_t *contents, uint32_t flags)
175
{
Libor Peltan's avatar
Libor Peltan committed
176 177 178
	if (ctx == NULL) {
		return KNOT_EINVAL;
	}
179 180 181

	ctx->contents = contents;

182
	ctx->node_ptrs = zone_tree_create(true);
Libor Peltan's avatar
Libor Peltan committed
183 184 185
	if (ctx->node_ptrs == NULL) {
		return KNOT_ENOMEM;
	}
186 187 188
	ctx->node_ptrs->flags = contents->nodes->flags;

	ctx->nsec3_ptrs = zone_tree_create(true);
Libor Peltan's avatar
Libor Peltan committed
189 190 191 192
	if (ctx->nsec3_ptrs == NULL) {
		zone_tree_free(&ctx->node_ptrs);
		return KNOT_ENOMEM;
	}
193
	ctx->nsec3_ptrs->flags = contents->nodes->flags;
Libor Peltan's avatar
Libor Peltan committed
194

195
	ctx->flags = flags;
Libor Peltan's avatar
Libor Peltan committed
196 197

	return KNOT_EOK;
198 199 200
}

int apply_prepare_zone_copy(zone_contents_t *old_contents,
Daniel Salzman's avatar
Daniel Salzman committed
201
                            zone_contents_t **new_contents)
202
{
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
	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;
}

226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
static int add_to_changes_cb2(zone_node_t *node, void *ctx)
{
	node->flags |= NODE_FLAGS_DELETED;
	int ret = zone_tree_insert(ctx, &node);
	assert(ret == KNOT_EOK);
	return ret;
}

static zone_node_t *find_node_in_changes(const knot_dname_t *owner, void *ctx)
{
	zone_tree_t *tree = ctx;
	zone_node_t *node = zone_tree_get(tree, owner);
	if (node == NULL) {
		node = node_new(owner, (tree->flags & ZONE_TREE_USE_BINODES),
		                (tree->flags & ZONE_TREE_BINO_SECOND), NULL);
		(void)zone_tree_insert(tree, &node);
	} else {
		node->flags &= ~NODE_FLAGS_DELETED;
	}
	return node;
}

248 249 250
int apply_add_rr(apply_ctx_t *ctx, const knot_rrset_t *rr)
{
	zone_contents_t *contents = ctx->contents;
251 252 253 254 255 256
	bool nsec3rel = knot_rrset_is_nsec3rel(rr);
	zone_tree_t *ptrs = nsec3rel ? ctx->nsec3_ptrs : ctx->node_ptrs;
	zone_tree_t *tree = zone_contents_tree_for_rr(contents, rr);
	if (tree == NULL) {
		return KNOT_ENOMEM;
	}
257

258 259 260 261 262 263
	// Get or create node with this owner, search changes first
	zone_node_t *node = NULL;
	int ret = zone_tree_add_node(tree, contents->apex, rr->owner, find_node_in_changes, ptrs, &node);
	if (ret != KNOT_EOK) {
		return ret;
	}
264
	if (node == NULL) {
265
		return KNOT_EOUTOFZONE;
266 267
	}

268 269 270 271
	ret = zone_tree_insert(nsec3rel ? ctx->nsec3_ptrs : ctx->node_ptrs, &node);
	if (ret != KNOT_EOK) {
		return ret;
	}
272

273 274 275
	if (binode_rdata_shared(node, rr->type)) {
		// Modifying existing RRSet.
		ret = replace_rdataset_with_copy(node, rr->type);
276 277
		if (ret != KNOT_EOK) {
			return ret;
278
		}
279
	}
280

281
	// Insert new RR to RRSet, data will be copied.
282 283 284 285 286 287
	ret = node_add_rrset(node, rr, NULL);
	if (ret == KNOT_ETTL) {
		char buff[KNOT_DNAME_TXT_MAXLEN + 1];
		char *owner = knot_dname_to_str(buff, rr->owner, sizeof(buff));
		if (owner == NULL) {
			owner = "";
288
		}
289 290 291 292 293 294
		char type[16] = { '\0' };
		knot_rrtype_to_string(rr->type, type, sizeof(type));
		log_zone_notice(contents->apex->owner,
		                "TTL mismatch, owner %s, type %s, "
		                "TTL set to %u", owner, type, rr->ttl);
		return KNOT_EOK;
295
	}
296
	return ret;
297 298
}

299
int apply_remove_rr(apply_ctx_t *ctx, const knot_rrset_t *rr)
300
{
301
	zone_contents_t *contents = ctx->contents;
302 303 304 305 306 307
	bool nsec3rel = knot_rrset_is_nsec3rel(rr);
	zone_tree_t *ptrs = nsec3rel ? ctx->nsec3_ptrs : ctx->node_ptrs;
	zone_tree_t *tree = zone_contents_tree_for_rr(contents, rr);
	if (tree == NULL) {
		return KNOT_ENOMEM;
	}
308

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

320
	int ret = zone_tree_insert(ptrs, &node);
321 322
	if (ret != KNOT_EOK) {
		return ret;
323 324
	}

325 326 327 328 329
	if (binode_rdata_shared(node, rr->type)) {
		ret = replace_rdataset_with_copy(node, rr->type);
		if (ret != KNOT_EOK) {
			return ret;
		}
330 331
	}

332 333
	knot_rdataset_t *changed_rrs = node_rdataset(node, rr->type);
	// Subtract changeset RRS from node RRS.
334
	ret = knot_rdataset_subtract(changed_rrs, &rr->rrs, NULL);
335
	if (ret != KNOT_EOK) {
336
		clear_new_rrs(node, rr->type);
337 338 339
		return ret;
	}

340
	if (changed_rrs->count == 0) {
341
		// RRSet is empty now, remove it from node, all data freed, except additionals.
342 343
		node_remove_rdataset(node, rr->type);
		// If node is empty now, delete it from zone tree.
344 345
		if (node->rrset_count == 0 && node->children == 0 && node != contents->apex) {
			zone_tree_del_node(tree, node, add_to_changes_cb2, ptrs);
346 347
		}
	}
348

349 350
	return KNOT_EOK;
}
Lubos Slovak's avatar
Lubos Slovak committed
351

352
int apply_replace_soa(apply_ctx_t *ctx, const changeset_t *chset)
353
{
354
	zone_contents_t *contents = ctx->contents;
355

356 357 358 359
	if (!knot_dname_is_equal(chset->soa_to->owner, contents->apex->owner)) {
		return KNOT_EDENIED;
	}

360 361
	assert(chset->soa_from && chset->soa_to);
	int ret = apply_remove_rr(ctx, chset->soa_from);
362 363 364 365
	if (ret != KNOT_EOK) {
		return ret;
	}

366 367
	// Check for SOA with proper serial but different rdata.
	if (node_rrtype_exists(contents->apex, KNOT_RRTYPE_SOA)) {
368
		return KNOT_ESOAINVAL;
369
	}
Jan Včelák's avatar
Jan Včelák committed
370

371
	return apply_add_rr(ctx, chset->soa_to);
372
}
373

374
int apply_changesets_directly(apply_ctx_t *ctx, list_t *chsets)
375
{
376
	if (ctx == NULL || ctx->contents == NULL || chsets == NULL) {
377 378 379
		return KNOT_EINVAL;
	}

380
	changeset_t *set = NULL;
381
	WALK_LIST(set, *chsets) {
382
		int ret = apply_single(ctx, set);
383 384 385 386 387
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

Libor Peltan's avatar
Libor Peltan committed
388
	return KNOT_EOK;
389 390
}

391
int apply_changeset_directly(apply_ctx_t *ctx, const changeset_t *ch)
392
{
393
	if (ctx == NULL || ctx->contents == NULL || ch == NULL) {
394 395
		return KNOT_EINVAL;
	}
396

397
	int ret = apply_single(ctx, ch);
398 399 400
	if (ret != KNOT_EOK) {
		return ret;
	}
401

402 403
	return KNOT_EOK;
}
404

405
void update_cleanup(apply_ctx_t *ctx)
406
{
Daniel Salzman's avatar
Daniel Salzman committed
407 408
	if (ctx == NULL) {
		return;
409
	}
Daniel Salzman's avatar
Daniel Salzman committed
410

411 412
	zone_trees_unify_binodes(ctx->node_ptrs, ctx->nsec3_ptrs);

Libor Peltan's avatar
Libor Peltan committed
413 414 415
	zone_tree_free(&ctx->node_ptrs);
	zone_tree_free(&ctx->nsec3_ptrs);

416 417 418 419 420
	// this is important not only for full update
	// but also for incremental because during adjusting
	// also the nodes not being affected by the update itself
	// might be affected
	zone_trees_unify_binodes(ctx->contents->nodes, ctx->contents->nsec3_nodes);
421 422

	if (ctx->cow_mutex != NULL) {
423
		sem_post(ctx->cow_mutex);
424
	}
425 426
}

427
void update_rollback(apply_ctx_t *ctx)
428
{
Daniel Salzman's avatar
Daniel Salzman committed
429 430
	if (ctx == NULL) {
		return;
431
	}
Daniel Salzman's avatar
Daniel Salzman committed
432

433 434 435 436 437 438 439 440
	if (ctx->node_ptrs != NULL) {
		ctx->node_ptrs->flags ^= ZONE_TREE_BINO_SECOND;
	}
	if (ctx->nsec3_ptrs != NULL) {
		ctx->nsec3_ptrs->flags ^= ZONE_TREE_BINO_SECOND;
	}
	zone_trees_unify_binodes(ctx->node_ptrs, ctx->nsec3_ptrs);

Libor Peltan's avatar
Libor Peltan committed
441 442
	zone_tree_free(&ctx->node_ptrs);
	zone_tree_free(&ctx->nsec3_ptrs);
443 444

	trie_cow_rollback(ctx->contents->nodes->cow, trie_cb_noop, NULL);
Libor Peltan's avatar
Libor Peltan committed
445
	ctx->contents->nodes->cow = NULL;
446 447
	if (ctx->contents->nsec3_nodes != NULL) {
		trie_cow_rollback(ctx->contents->nsec3_nodes->cow, trie_cb_noop, NULL);
Libor Peltan's avatar
Libor Peltan committed
448
		ctx->contents->nsec3_nodes->cow = NULL;
449 450
	}

451
	if (ctx->cow_mutex != NULL) {
452
		sem_post(ctx->cow_mutex);
453 454
	}

455 456 457 458 459 460
	free(ctx->contents->nodes);
	free(ctx->contents->nsec3_nodes);

	dnssec_nsec3_params_free(&ctx->contents->nsec3_params);

	free(ctx->contents);
461 462
}

463
void update_free_zone(zone_contents_t *contents)
464
{
465
	if (contents == NULL) {
Daniel Salzman's avatar
Daniel Salzman committed
466 467
		return;
	}
468

469
	trie_cow_commit(contents->nodes->cow, trie_cb_noop, NULL);
Libor Peltan's avatar
Libor Peltan committed
470
	contents->nodes->cow = NULL;
471 472
	if (contents->nsec3_nodes != NULL) {
		trie_cow_commit(contents->nsec3_nodes->cow, trie_cb_noop, NULL);
Libor Peltan's avatar
Libor Peltan committed
473
		contents->nsec3_nodes->cow = NULL;
474 475 476 477
	}

	free(contents->nodes);
	free(contents->nsec3_nodes);
478

479
	dnssec_nsec3_params_free(&contents->nsec3_params);
Daniel Salzman's avatar
Daniel Salzman committed
480

481
	free(contents);
482
}