xfr-in.c 24.3 KB
Newer Older
1
/*  Copyright (C) 2011 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
#include <assert.h>
18
#include <urcu.h>
19

20 21
#include "knot/server/journal.h"

22
#include "knot/updates/xfr-in.h"
23

24
#include "libknot/packet/wire.h"
25
#include "common/debug.h"
26
#include "libknot/packet/pkt.h"
27
#include "libknot/processing/process.h"
28
#include "libknot/dname.h"
29
#include "knot/zone/zone.h"
30
#include "knot/zone/zonefile.h"
31 32
#include "knot/dnssec/zone-nsec.h"
#include "knot/dnssec/zone-sign.h"
33
#include "libknot/dnssec/random.h"
34
#include "libknot/common.h"
35
#include "knot/updates/changesets.h"
36
#include "libknot/rdata/tsig.h"
37
#include "libknot/tsig-op.h"
38
#include "knot/zone/semantic-check.h"
39
#include "common/lists.h"
40
#include "common/descriptor.h"
41
#include "libknot/util/utils.h"
42
#include "libknot/rdata/soa.h"
43
#include "knot/nameserver/axfr.h"
44
#include "knot/nameserver/ixfr.h"
45

46 47
#define KNOT_NS_TSIG_FREQ 100

48 49 50 51 52 53
/*!
 * \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)
 */
54 55 56 57 58
static void rrs_list_clear(list_t *l, mm_ctx_t *mm)
{
	ptrnode_t *n;
	node_t *nxt;
	WALK_LIST_DELSAFE(n, nxt, *l) {
59
		mm_free(mm, (void *)n->d);
60 61 62 63
		mm_free(mm, n);
	};
}

64 65 66 67 68 69 70 71 72 73
static int knot_ns_tsig_required(int packet_nr)
{
	/*! \bug This can overflow to negative numbers. Proper solution is to
	 *       count exactly at one place for each incoming/outgoing packet
	 *       with packet_nr = (packet_nr + 1) % FREQ and require TSIG on 0.
	 */
	dbg_ns_verb("ns_tsig_required(%d): %d\n", packet_nr,
	            (packet_nr % KNOT_NS_TSIG_FREQ == 0));
	return (packet_nr % KNOT_NS_TSIG_FREQ == 0);
}
74

Lubos Slovak's avatar
Lubos Slovak committed
75
/*----------------------------------------------------------------------------*/
Lubos Slovak's avatar
Lubos Slovak committed
76 77
/* API functions                                                              */
/*----------------------------------------------------------------------------*/
Lubos Slovak's avatar
Lubos Slovak committed
78

79
int xfrin_transfer_needed(const zone_contents_t *zone,
80
                          knot_pkt_t *soa_response)
81
{
82 83 84
	/*
	 * Retrieve the local Serial
	 */
85
	const knot_rdataset_t *soa_rrs =
86
		node_rdataset(zone->apex, KNOT_RRTYPE_SOA);
87
	if (soa_rrs == NULL) {
88
		char *name = knot_dname_to_str(zone->apex->owner);
89
		dbg_xfrin("SOA RRSet missing in the zone %s!\n", name);
90
		free(name);
Lubos Slovak's avatar
Lubos Slovak committed
91
		return KNOT_ERROR;
92 93
	}

94
	uint32_t local_serial = knot_soa_serial(soa_rrs);
95 96 97 98
	/*
	 * Retrieve the remote Serial
	 */
	// the SOA should be the first (and only) RRSet in the response
99
	const knot_pktsection_t *answer = knot_pkt_section(soa_response, KNOT_ANSWER);
100
	if (answer->count < 1) {
Lubos Slovak's avatar
Lubos Slovak committed
101
		return KNOT_EMALF;
102
	}
103 104
	knot_rrset_t soa_rr = answer->rr[0];
	if (soa_rr.type != KNOT_RRTYPE_SOA) {
Lubos Slovak's avatar
Lubos Slovak committed
105
		return KNOT_EMALF;
106 107
	}

108
	uint32_t remote_serial = knot_soa_serial(&soa_rr.rrs);
109
	return (knot_serial_compare(local_serial, remote_serial) < 0);
110 111 112 113
}

/*----------------------------------------------------------------------------*/

114
static int xfrin_check_tsig(knot_pkt_t *packet, knot_ns_xfr_t *xfr,
115
                            int tsig_req)
116
{
117
#warning reimplement, but not inside this file
118 119
	assert(packet != NULL);
	assert(xfr != NULL);
Jan Včelák's avatar
Jan Včelák committed
120 121 122 123

	dbg_xfrin_verb("xfrin_check_tsig(): packet nr: %d, required: %d\n",
		       xfr->packet_nr, tsig_req);

124 125 126 127 128
	/*
	 * If we are expecting it (i.e. xfr->prev_digest_size > 0)
	 *   a) it should be there (first, last or each 100th packet) and it
	 *      is not
	 *        Then we should discard the changes and close the connection.
Jan Včelák's avatar
Jan Včelák committed
129
	 *   b) it should be there and it is or it may not be there (other
130 131 132 133 134 135 136
	 *      packets) and it is
	 *        We validate the TSIG and reset packet number counting and
	 *        data aggregation.
	 *
	 * If we are not expecting it (i.e. xfr->prev_digest_size <= 0) and
	 * it is there => it should probably be considered an error
	 */
Jan Včelák's avatar
Jan Včelák committed
137

138
	int ret = KNOT_EOK;
139 140
	if (xfr->tsig_key) {
		// just append the wireformat to the TSIG data
141
		uint8_t *wire_buf = xfr->tsig_data + xfr->tsig_data_size;
142 143
		memcpy(wire_buf, packet->wire, packet->size);
		xfr->tsig_data_size += packet->size;
144
	}
Jan Včelák's avatar
Jan Včelák committed
145

Lubos Slovak's avatar
Lubos Slovak committed
146
	if (xfr->tsig_key) {
147
		if (tsig_req && packet->tsig_rr == NULL) {
148
			// TSIG missing!!
149
			return KNOT_ENOTSIG;
150
		} else if (packet->tsig_rr != NULL) {
151 152
			// TSIG there, either required or not, process
			if (xfr->packet_nr == 0) {
153
				ret = knot_tsig_client_check(packet->tsig_rr,
154
					xfr->tsig_data, xfr->tsig_data_size,
Lubos Slovak's avatar
Lubos Slovak committed
155
					xfr->digest, xfr->digest_size,
156 157
					xfr->tsig_key,
					xfr->tsig_prev_time_signed);
158
			} else {
159
				ret = knot_tsig_client_check_next(packet->tsig_rr,
Jan Včelák's avatar
Jan Včelák committed
160
					xfr->tsig_data, xfr->tsig_data_size,
Lubos Slovak's avatar
Lubos Slovak committed
161
					xfr->digest, xfr->digest_size,
162 163
					xfr->tsig_key,
					xfr->tsig_prev_time_signed);
164
			}
Jan Včelák's avatar
Jan Včelák committed
165

166
			if (ret != KNOT_EOK) {
Lubos Slovak's avatar
Lubos Slovak committed
167 168
				/* No need to check TSIG error
				 * here, propagate and check elsewhere.*/
169 170
				return ret;
			}
Jan Včelák's avatar
Jan Včelák committed
171

Lubos Slovak's avatar
Lubos Slovak committed
172 173
			// and reset the data storage
			//xfr->packet_nr = 1;
174
			xfr->tsig_data_size = 0;
175 176

			// Extract the digest from the TSIG RDATA and store it.
177
			if (xfr->digest_max_size < tsig_rdata_mac_length(packet->tsig_rr)) {
Lubos Slovak's avatar
Lubos Slovak committed
178 179
				return KNOT_ESPACE;
			}
180 181 182
			memcpy(xfr->digest, tsig_rdata_mac(packet->tsig_rr),
			       tsig_rdata_mac_length(packet->tsig_rr));
			xfr->digest_size = tsig_rdata_mac_length(packet->tsig_rr);
183 184 185 186

			// Extract the time signed from the TSIG and store it
			// We may rewrite the tsig_req_time_signed field
			xfr->tsig_prev_time_signed =
187
					tsig_rdata_time_signed(packet->tsig_rr);
Jan Včelák's avatar
Jan Včelák committed
188

Lubos Slovak's avatar
Lubos Slovak committed
189
		}
190
	} else if (packet->tsig_rr != NULL) {
191 192 193
		// TSIG where it should not be
		return KNOT_EMALF;
	}
Jan Včelák's avatar
Jan Včelák committed
194

195 196 197 198 199
	return KNOT_EOK;
}

/*----------------------------------------------------------------------------*/

200
static void xfrin_take_rr(const knot_pktsection_t *answer, const knot_rrset_t **rr, uint16_t *cur)
201 202
{
	if (*cur < answer->count) {
203
		*rr = &answer->rr[*cur];
204
		*cur += 1;
205
	} else {
206
		*rr = NULL;
207
	}
208
}
Jan Včelák's avatar
Jan Včelák committed
209

210
/*----------------------------------------------------------------------------*/
211

212
int xfrin_process_axfr_packet(knot_pkt_t *pkt, struct xfr_proc *proc)
213
{
214
	if (pkt == NULL) {
215
		return KNOT_EINVAL;
216 217
	}

218
	int ret = KNOT_EOK;
219
	uint16_t rr_id = 0;
220
	const knot_rrset_t *rr = NULL;
221
	const knot_pktsection_t *answer = knot_pkt_section(pkt, KNOT_ANSWER);
222

223
	xfrin_take_rr(answer, &rr, &rr_id);
224
	++proc->npkts;
225

226
	// Init zone creator
227
	zcreator_t zc = {.z = proc->zone,
Lubos Slovak's avatar
Lubos Slovak committed
228
	                 .master = false, .ret = KNOT_EOK };
229

230
	while (rr) {
231
		if (rr->type == KNOT_RRTYPE_SOA &&
Jan Kadlec's avatar
Jan Kadlec committed
232
		    node_rrtype_exists(zc.z->apex, KNOT_RRTYPE_SOA)) {
233
			// Last SOA, last message, check TSIG.
234 235
//			int ret = xfrin_check_tsig(pkt, xfr, 1);
#warning TODO: TSIG API
236 237 238
			if (ret != KNOT_EOK) {
				return ret;
			}
239
			return 1; // Signal that transfer finished.
240
		} else {
241
			int ret = zcreator_step(&zc, rr);
Lubos Slovak's avatar
Lubos Slovak committed
242
			if (ret != KNOT_EOK) {
243
				// 'rr' is either inserted, or free'd
244
				return ret;
245
			}
246
			xfrin_take_rr(answer, &rr, &rr_id);
247
		}
248 249
	}

250
	// Check possible TSIG at the end of DNS message.
251 252 253
//	return xfrin_check_tsig(pkt, xfr, knot_ns_tsig_required(xfr->packet_nr));
#warning TODO: TSIG API
	return KNOT_EOK;
254
}
255 256 257

/*----------------------------------------------------------------------------*/

258 259 260 261
/*----------------------------------------------------------------------------*/
/* Applying changesets to zone                                                */
/*----------------------------------------------------------------------------*/

262
void xfrin_cleanup_successful_update(knot_changesets_t *chgs)
263
{
264 265 266 267
	if (chgs == NULL) {
		return;
	}

268 269 270 271
	knot_changeset_t *change = NULL;
	WALK_LIST(change, chgs->sets) {
		// Delete old RR data
		rrs_list_clear(&change->old_data, NULL);
272
		init_list(&change->old_data);
273 274 275 276
		// Keep new RR data
		ptrlist_free(&change->new_data, NULL);
		init_list(&change->new_data);
	};
277
}
Jan Včelák's avatar
Jan Včelák committed
278

279 280
/*----------------------------------------------------------------------------*/

Jan Kadlec's avatar
Jan Kadlec committed
281
static int free_additional(zone_node_t **node, void *data)
282 283
{
	UNUSED(data);
Jan Kadlec's avatar
Jan Kadlec committed
284
	if ((*node)->flags & NODE_FLAGS_NONAUTH) {
285 286 287 288
		// non-auth nodes have no additionals.
		return KNOT_EOK;
	}

289 290 291 292 293 294 295 296 297 298 299
	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;
}

300
void xfrin_zone_contents_free(zone_contents_t **contents)
301 302 303
{
	// free the zone tree, but only the structure
	// (nodes are already destroyed)
304
	dbg_zone("Destroying zone tree.\n");
305 306
	// free additional arrays
	knot_zone_tree_apply((*contents)->nodes, free_additional, NULL);
307
	knot_zone_tree_deep_free(&(*contents)->nodes);
308
	dbg_zone("Destroying NSEC3 zone tree.\n");
309
	knot_zone_tree_deep_free(&(*contents)->nsec3_nodes);
310

311
	knot_nsec3param_free(&(*contents)->nsec3_params);
Jan Včelák's avatar
Jan Včelák committed
312

313 314 315
	free(*contents);
	*contents = NULL;
}
316 317 318

/*----------------------------------------------------------------------------*/

319
static void xfrin_cleanup_failed_update(zone_contents_t **new_contents)
320
{
321
	if (new_contents == NULL) {
322
		return;
323
	}
Jan Včelák's avatar
Jan Včelák committed
324

325 326
	if (*new_contents != NULL) {
		// destroy the shallow copy of zone
327
		xfrin_zone_contents_free(new_contents);
328
	}
329 330 331 332
}

/*----------------------------------------------------------------------------*/

333
void xfrin_rollback_update(knot_changesets_t *chgs,
334
                           zone_contents_t **new_contents)
335
{
336 337 338 339 340
	if (chgs != NULL) {
		knot_changeset_t *change = NULL;
		WALK_LIST(change, chgs->sets) {
			// Delete new RR data
			rrs_list_clear(&change->new_data, NULL);
341
			init_list(&change->new_data);
342 343
			// Keep old RR data
			ptrlist_free(&change->old_data, NULL);
344
			init_list(&change->old_data);
345 346
		};
	}
347
	xfrin_cleanup_failed_update(new_contents);
348
}
349

350
/*----------------------------------------------------------------------------*/
Lubos Slovak's avatar
Lubos Slovak committed
351

352
static int xfrin_replace_rrs_with_copy(zone_node_t *node,
353
                                       uint16_t type)
354
{
355
	// Find data to copy.
356 357 358 359
	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];
360
			break;
361
		}
362
	}
363
	assert(data);
364

365
	// Create new data.
366 367
	knot_rdataset_t *rrs = &data->rrs;
	void *copy = malloc(knot_rdataset_size(rrs));
368 369
	if (copy == NULL) {
		return KNOT_ENOMEM;
370
	}
Jan Včelák's avatar
Jan Včelák committed
371

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

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

Lubos Slovak's avatar
Lubos Slovak committed
377
	return KNOT_EOK;
378 379
}

380
static void clear_new_rrs(zone_node_t *node, uint16_t type)
381
{
382
	knot_rdataset_t *new_rrs = node_rdataset(node, type);
383
	if (new_rrs) {
384
		knot_rdataset_clear(new_rrs, NULL);
385
	}
386
}
Jan Včelák's avatar
Jan Včelák committed
387

388
static bool can_remove(const zone_node_t *node, const knot_rrset_t *rr)
389 390
{
	if (node == NULL) {
391
		// Node does not exist, cannot remove anything.
392
		return false;
393
	}
394
	const knot_rdataset_t *node_rrs = node_rdataset(node, rr->type);
395
	if (node_rrs == NULL) {
396
		// Node does not have this type at all.
397
		return false;
398
	}
Jan Včelák's avatar
Jan Včelák committed
399

400
	const bool compare_ttls = false;
401
	for (uint16_t i = 0; i < rr->rrs.rr_count; ++i) {
402 403
		knot_rdata_t *rr_cmp = knot_rdataset_at(&rr->rrs, i);
		if (knot_rdataset_member(node_rrs, rr_cmp, compare_ttls)) {
404
			// At least one RR matches.
405
			return true;
406 407
		}
	}
Jan Včelák's avatar
Jan Včelák committed
408

409
	// Node does have the type, but no RRs match.
410
	return false;
411 412
}

413
static int add_old_data(knot_changeset_t *chset, knot_rdata_t *old_data)
414
{
415 416
	if (ptrlist_add(&chset->old_data, old_data, NULL) == NULL) {
		return KNOT_ENOMEM;
417 418
	}

419
	return KNOT_EOK;
420 421
}

422
static int add_new_data(knot_changeset_t *chset, knot_rdata_t *new_data)
Lubos Slovak's avatar
Lubos Slovak committed
423
{
424 425
	if (ptrlist_add(&chset->new_data, new_data, NULL) == NULL) {
		return KNOT_ENOMEM;
426 427
	}

428 429
	return KNOT_EOK;
}
Lubos Slovak's avatar
Lubos Slovak committed
430

431
static int remove_rr(zone_node_t *node, const knot_rrset_t *rr,
432 433
                     knot_changeset_t *chset)
{
434
	knot_rrset_t removed_rrset = node_rrset(node, rr->type);
435
	knot_rdata_t *old_data = removed_rrset.rrs.data;
436
	int ret = xfrin_replace_rrs_with_copy(node, rr->type);
Lubos Slovak's avatar
Lubos Slovak committed
437 438 439 440
	if (ret != KNOT_EOK) {
		return ret;
	}

441
	// Store old data for cleanup.
442
	ret = add_old_data(chset, old_data);
Jan Kadlec's avatar
Jan Kadlec committed
443
	if (ret != KNOT_EOK) {
444
		clear_new_rrs(node, rr->type);
Jan Kadlec's avatar
Jan Kadlec committed
445 446
		return ret;
	}
Lubos Slovak's avatar
Lubos Slovak committed
447

448
	knot_rdataset_t *changed_rrs = node_rdataset(node, rr->type);
449
	// Subtract changeset RRS from node RRS.
450
	ret = knot_rdataset_subtract(changed_rrs, &rr->rrs, NULL);
451 452
	if (ret != KNOT_EOK) {
		clear_new_rrs(node, rr->type);
Lubos Slovak's avatar
Lubos Slovak committed
453
		return ret;
454
	}
455

456
	if (changed_rrs->rr_count > 0) {
457
		// Subtraction left some data in RRSet, store it for rollback.
458
		ret = add_new_data(chset, changed_rrs->data);
459
		if (ret != KNOT_EOK) {
460
			knot_rdataset_clear(changed_rrs, NULL);
461 462
			return ret;
		}
463
	} else {
464
		// RRSet is empty now, remove it from node, all data freed.
465
		node_remove_rdataset(node, rr->type);
466 467
	}

468
	return KNOT_EOK;
469 470
}

471
static int xfrin_apply_remove(zone_contents_t *contents, knot_changeset_t *chset)
472
{
473
	knot_rr_ln_t *rr_node = NULL;
Jan Kadlec's avatar
Jan Kadlec committed
474
	WALK_LIST(rr_node, chset->remove) {
475
		const knot_rrset_t *rr = rr_node->rr;
476

477
		// Find node for this owner
478
		zone_node_t *node = zone_contents_find_node_for_rr(contents,
479 480
		                                                   rr);
		if (!can_remove(node, rr)) {
481
			// Nothing to remove from, skip.
482 483
			continue;
		}
484

485 486 487 488
		int ret = remove_rr(node, rr, chset);
		if (ret != KNOT_EOK) {
			return ret;
		}
489
	}
Jan Včelák's avatar
Jan Včelák committed
490

491
	return KNOT_EOK;
492 493
}

494
static int add_rr(zone_node_t *node, const knot_rrset_t *rr,
495
                  knot_changeset_t *chset, bool master)
496
{
497
	knot_rrset_t changed_rrset = node_rrset(node, rr->type);
498 499
	if (!knot_rrset_empty(&changed_rrset)) {
		// Modifying existing RRSet.
500
		knot_rdata_t *old_data = changed_rrset.rrs.data;
501
		int ret = xfrin_replace_rrs_with_copy(node, rr->type);
502 503
		if (ret != KNOT_EOK) {
			return ret;
504 505
		}

Jan Kadlec's avatar
Jan Kadlec committed
506
		// Store old RRS for cleanup.
507
		ret = add_old_data(chset, old_data);
508
		if (ret != KNOT_EOK) {
509
			clear_new_rrs(node, rr->type);
510
			return ret;
511
		}
512
	}
513

514
	// Insert new RR to RRSet, data will be copied.
515
	int ret = node_add_rrset(node, rr);
516 517 518 519 520 521 522
	if (ret == KNOT_EOK || ret == KNOT_ETTL) {
		// RR added, store for possible rollback.
		knot_rdataset_t *rrs = node_rdataset(node, rr->type);
		int data_ret = add_new_data(chset, rrs->data);
		if (data_ret != KNOT_EOK) {
			knot_rdataset_clear(rrs, NULL);
			return data_ret;
523 524
		}

525
		if (ret == KNOT_ETTL) {
526
			// Handle possible TTL errors.
527
			log_ttl_error(node, rr);
528 529 530
			if (!master) {
				// TTL errors fatal only for master.
				return KNOT_EOK;
531
			}
532
		}
533 534
	}

535
	return ret;
536 537
}

538
static int xfrin_apply_add(zone_contents_t *contents,
539
                           knot_changeset_t *chset, bool master)
540
{
541
	knot_rr_ln_t *rr_node = NULL;
542
	WALK_LIST(rr_node, chset->add) {
Jan Kadlec's avatar
Jan Kadlec committed
543
		knot_rrset_t *rr = rr_node->rr;
544

545
		// Get or create node with this owner
546
		zone_node_t *node = zone_contents_get_node_for_rr(contents, rr);
547 548
		if (node == NULL) {
			return KNOT_ENOMEM;
549 550
		}

551
		int ret = add_rr(node, rr, chset, master);
552
		if (ret != KNOT_EOK) {
553 554 555 556 557 558 559 560 561
			return ret;
		}
	}

	return KNOT_EOK;
}

/*----------------------------------------------------------------------------*/

562
static int xfrin_apply_replace_soa(zone_contents_t *contents,
563
                                   knot_changeset_t *chset)
564
{
565 566
	assert(chset->soa_from);
	int ret = remove_rr(contents->apex, chset->soa_from, chset);
567 568
	if (ret != KNOT_EOK) {
		return ret;
569 570
	}

571
	assert(!node_rrtype_exists(contents->apex, KNOT_RRTYPE_SOA));
572

573
	return add_rr(contents->apex, chset->soa_to, chset, false);
574 575 576 577
}

/*----------------------------------------------------------------------------*/

578
static int xfrin_apply_changeset(zone_contents_t *contents,
579
                                 knot_changeset_t *chset, bool master)
580 581 582 583 584 585 586
{
	/*
	 * 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.
	 */

587
	dbg_xfrin("APPLYING CHANGESET: from serial %u to serial %u\n",
Jan Včelák's avatar
Jan Včelák committed
588
		  chset->serial_from, chset->serial_to);
589

590
	// check if serial matches
591
	const knot_rdataset_t *soa = node_rdataset(contents->apex, KNOT_RRTYPE_SOA);
592
	if (soa == NULL || knot_soa_serial(soa) != chset->serial_from) {
593
		dbg_xfrin("SOA serials do not match!!\n");
594
		return KNOT_EINVAL;
595 596
	}

597
	int ret = xfrin_apply_remove(contents, chset);
598 599 600 601
	if (ret != KNOT_EOK) {
		return ret;
	}

602
	ret = xfrin_apply_add(contents, chset, master);
603 604 605 606
	if (ret != KNOT_EOK) {
		return ret;
	}

607
	return xfrin_apply_replace_soa(contents, chset);
608 609 610 611
}

/*----------------------------------------------------------------------------*/

612 613 614
/*! \brief Wrapper for BIRD lists. Storing: Node. */
typedef struct knot_node_ln {
	node_t n; /*!< List node. */
615
	zone_node_t *node; /*!< Actual usable data. */
616 617
} knot_node_ln_t;

618
static int add_node_to_list(zone_node_t *node, list_t *l)
Lubos Slovak's avatar
Lubos Slovak committed
619
{
620 621 622 623 624 625 626 627 628
	assert(node && l);
	knot_node_ln_t *data = malloc(sizeof(knot_node_ln_t));
	if (data == NULL) {
		return KNOT_ENOMEM;
	}
	data->node = node;
	add_head(l, (node_t *)data);
	return KNOT_EOK;
}
Lubos Slovak's avatar
Lubos Slovak committed
629

630
static int xfrin_mark_empty(zone_node_t **node_p, void *data)
631 632
{
	assert(node_p && *node_p);
633
	zone_node_t *node = *node_p;
634 635 636
	list_t *l = (list_t *)data;
	assert(data);
	if (node->rrset_count == 0 && node->children == 0 &&
637
	    !(node->flags & NODE_FLAGS_EMPTY)) {
638
		/*!
Jan Kadlec's avatar
Jan Kadlec committed
639 640
		 * Mark this node and all parent nodes that have 0 RRSets and
		 * no children for removal.
641 642
		 */
		int ret = add_node_to_list(node, l);
Lubos Slovak's avatar
Lubos Slovak committed
643
		if (ret != KNOT_EOK) {
644
			return ret;
Lubos Slovak's avatar
Lubos Slovak committed
645
		}
646
		node->flags |= NODE_FLAGS_EMPTY;
647
		if (node->parent) {
648
			if ((node->parent->flags & NODE_FLAGS_WILDCARD_CHILD)
649
			    && knot_dname_is_wildcard(node->owner)) {
650
				node->parent->flags &= ~NODE_FLAGS_WILDCARD_CHILD;
651
			}
652 653 654
			node->parent->children--;
			// Recurse using the parent node
			return xfrin_mark_empty(&node->parent, data);
Lubos Slovak's avatar
Lubos Slovak committed
655
		}
Lubos Slovak's avatar
Lubos Slovak committed
656
	}
657 658

	return KNOT_EOK;
659
}
660 661 662

/*----------------------------------------------------------------------------*/

663
static int xfrin_remove_empty_nodes(zone_contents_t *z)
Lubos Slovak's avatar
Lubos Slovak committed
664
{
665 666
	dbg_xfrin("Removing empty nodes from zone.\n");

667 668
	list_t l;
	init_list(&l);
Lubos Slovak's avatar
Lubos Slovak committed
669
	// walk through the zone and select nodes to be removed
670 671 672 673
	int ret = knot_zone_tree_apply(z->nodes,
	                               xfrin_mark_empty, &l);
	if (ret != KNOT_EOK) {
		return ret;
Lubos Slovak's avatar
Lubos Slovak committed
674
	}
675

676 677 678 679
	node_t *n = NULL;
	node_t *nxt = NULL;
	WALK_LIST_DELSAFE(n, nxt, l) {
		knot_node_ln_t *list_node = (knot_node_ln_t *)n;
680
		ret = zone_contents_remove_node(z, list_node->node->owner);
Jan Kadlec's avatar
Jan Kadlec committed
681
		if (ret != KNOT_EOK) {
682
			return ret;
Lubos Slovak's avatar
Lubos Slovak committed
683
		}
684
		node_free(&list_node->node);
685
		free(n);
Lubos Slovak's avatar
Lubos Slovak committed
686 687
	}

688 689 690 691 692 693
	init_list(&l);
	// Do the same with NSEC3 nodes.
	ret = knot_zone_tree_apply(z->nsec3_nodes,
	                           xfrin_mark_empty, &l);
	if (ret != KNOT_EOK) {
		return ret;
Lubos Slovak's avatar
Lubos Slovak committed
694
	}
695

696 697
	WALK_LIST_DELSAFE(n, nxt, l) {
		knot_node_ln_t *list_node = (knot_node_ln_t *)n;
698
		ret = zone_contents_remove_nsec3_node(z, list_node->node->owner);
699
		if (ret != KNOT_EOK) {
Jan Kadlec's avatar
Jan Kadlec committed
700
			return ret;
701
		}
702
		node_free(&list_node->node);
703
		free(n);
704 705
	}

Lubos Slovak's avatar
Lubos Slovak committed
706 707 708 709 710
	return KNOT_EOK;
}

/*----------------------------------------------------------------------------*/

711
static int xfrin_prepare_zone_copy(zone_contents_t *old_contents, zone_contents_t **new_contents)
712
{
713
	if (old_contents == NULL || new_contents == NULL) {
Marek Vavrusa's avatar
Marek Vavrusa committed
714
		return KNOT_EINVAL;
715 716
	}

717
	dbg_xfrin("Preparing zone copy...\n");
718

719 720 721
	/*
	 * Ensure that the zone generation is set to 0.
	 */
722
	if (!zone_contents_gen_is_old(old_contents)) {
723 724 725
		// this would mean that a previous update was not completed
		// abort
		dbg_zone("Trying to apply changesets to zone that is "
Jan Včelák's avatar
Jan Včelák committed
726
				  "being updated. Aborting.\n");
727 728 729 730 731 732 733 734
		return KNOT_EAGAIN;
	}

	/*
	 * Create a shallow copy of the zone, so that the structures may be
	 * updated.
	 *
	 * This will create new zone contents structures (normal nodes' tree,
735
	 * NSEC3 tree), and copy all nodes.
736 737
	 * The data in the nodes (RRSets) remain the same though.
	 */
738
	zone_contents_t *contents_copy = NULL;
739

740
	dbg_xfrin("Copying zone contents.\n");
741
	int ret = zone_contents_shallow_copy(old_contents, &contents_copy);
742 743
	if (ret != KNOT_EOK) {
		dbg_xfrin("Failed to create shallow copy of zone: %s\n",
Jan Včelák's avatar
Jan Včelák committed
744
			  knot_strerror(ret));
745 746 747
		return ret;
	}

748
	assert(contents_copy->apex != NULL);
Lubos Slovak's avatar
Lubos Slovak committed
749

750
	*new_contents = contents_copy;
Jan Včelák's avatar
Jan Včelák committed
751

752 753
	return KNOT_EOK;
}
754

755
/*----------------------------------------------------------------------------*/
756

757
int xfrin_finalize_updated_zone(zone_contents_t *contents_copy,
758
                                bool set_nsec3_names)
759
{
760
	if (contents_copy == NULL) {
761 762
		return KNOT_EINVAL;
	}
Jan Včelák's avatar
Jan Včelák committed
763

764 765
	/*
	 * Finalize the new zone contents:
Lubos Slovak's avatar
Lubos Slovak committed
766
	 * - delete empty nodes
767 768 769
	 * - parse NSEC3PARAM
	 * - do adjusting of nodes and RDATA
	 * - ???
Jan Kadlec's avatar
Jan Kadlec committed
770
	 * - PROFIT
771
	 */
Jan Včelák's avatar
Jan Včelák committed
772

773
	int ret = xfrin_remove_empty_nodes(contents_copy);
774
	if (ret != KNOT_EOK) {
775
		dbg_xfrin("Failed to remove empty nodes: %s\n",
Jan Včelák's avatar
Jan Včelák committed
776
			  knot_strerror(ret));
777 778
		return ret;
	}
779

780
	dbg_xfrin("Adjusting zone contents.\n");
781
	if (set_nsec3_names) {
782
		ret = zone_contents_adjust_full(contents_copy, NULL, NULL);
783
	} else {
784
		ret = zone_contents_adjust_pointers(contents_copy);
785
	}
Lubos Slovak's avatar
Lubos Slovak committed
786
	if (ret != KNOT_EOK) {
787
		dbg_xfrin("Failed to finalize zone contents: %s\n",
Jan Včelák's avatar
Jan Včelák committed
788
			  knot_strerror(ret));
789 790
		return ret;
	}
791

792
	assert(contents_copy->apex != NULL);
793

794 795 796 797 798
	return KNOT_EOK;
}

/*----------------------------------------------------------------------------*/

799
int xfrin_apply_changesets_directly(zone_contents_t *contents,
800 801
                                    knot_changesets_t *chsets)
{
802
	if (contents == NULL || chsets == NULL) {
803 804 805 806 807
		return KNOT_EINVAL;
	}

	knot_changeset_t *set = NULL;
	WALK_LIST(set, chsets->sets) {
808 809
		const bool master = true; // Only DNSSEC changesets are applied directly.
		int ret = xfrin_apply_changeset(contents, set, master);
810
		if (ret != KNOT_EOK) {
811
			xfrin_cleanup_successful_update(chsets);
812 813 814 815
			return ret;
		}
	}

816 817 818 819 820 821 822 823
	int ret = xfrin_finalize_updated_zone(contents, true);

	/*
	 * HACK: Cleanup for successful update is used for both success and fail
	 * when modifying the zone directly, will fix in new zone API.
	 */
	xfrin_cleanup_successful_update(chsets);
	return ret;
824 825 826 827
}

/*----------------------------------------------------------------------------*/

828
int xfrin_apply_changesets(zone_t *zone,
829
                           knot_changesets_t *chsets,
830
                           zone_contents_t **new_contents)
831
{
832
	if (zone == NULL || chsets == NULL || EMPTY_LIST(chsets->sets)
833 834 835 836
	    || new_contents == NULL) {
		return KNOT_EINVAL;
	}

837
	zone_contents_t *old_contents = zone->contents;
838 839 840 841 842 843 844 845
	if (!old_contents) {
		dbg_xfrin("Cannot apply changesets to empty zone.\n");
		return KNOT_EINVAL;
	}

	dbg_xfrin("Applying changesets to zone...\n");

	dbg_xfrin_verb("Creating shallow copy of the zone...\n");
846
	zone_contents_t *contents_copy = NULL;
847
	int ret = xfrin_prepare_zone_copy(old_contents, &contents_copy);
848 849
	if (ret != KNOT_EOK) {
		dbg_xfrin("Failed to prepare zone copy: %s\n",
Jan Včelák's avatar
Jan Včelák committed
850
			  knot_strerror(ret));
851 852 853 854 855 856 857 858
		return ret;
	}

	/*
	 * Apply the changesets.
	 */
	dbg_xfrin("Applying changesets.\n");
	dbg_xfrin_verb("Old contents apex: %p, new apex: %p\n",
Jan Včelák's avatar
Jan Včelák committed
859
		       old_contents->apex, contents_copy->apex);
Jan Kadlec's avatar
Jan Kadlec committed
860
	knot_changeset_t *set = NULL;
861
	const bool master = (zone_master(zone) == NULL);
Jan Kadlec's avatar
Jan Kadlec committed
862
	WALK_LIST(set, chsets->sets) {
863
		ret = xfrin_apply_changeset(contents_copy, set, master);
Jan Kadlec's avatar
Jan Kadlec committed
864
		if (ret != KNOT_EOK) {
865
			xfrin_rollback_update(chsets, &contents_copy);
866
			dbg_xfrin("Failed to apply changesets to zone: "
Jan Včelák's avatar
Jan Včelák committed
867
				  "%s\n", knot_strerror(ret));
868 869 870
			return ret;
		}
	}
871

872
	assert(contents_copy->apex != NULL);
873 874 875 876 877 878

	/*!
	 * \todo Test failure of IXFR.
	 */

	dbg_xfrin_verb("Finalizing updated zone...\n");
879
	ret = xfrin_finalize_updated_zone(contents_copy, true);
880 881
	if (ret != KNOT_EOK) {
		dbg_xfrin("Failed to finalize updated zone: %s\n",
Jan Včelák's avatar
Jan Včelák committed
882
			  knot_strerror(ret));
883
		xfrin_rollback_update(chsets, &contents_copy);
884 885
		return ret;
	}
Jan Včelák's avatar
Jan Včelák committed
886

887 888 889 890 891 892 893
	*new_contents = contents_copy;

	return KNOT_EOK;
}

/*----------------------------------------------------------------------------*/

894
zone_contents_t *xfrin_switch_zone(zone_t *zone, zone_contents_t *new_contents)
895
{
896
	if (zone == NULL || new_contents == NULL) {
897
		return NULL;
898
	}
Lubos Slovak's avatar
Lubos Slovak committed
899

Lubos Slovak's avatar
Lubos Slovak committed
900
	dbg_xfrin("Switching zone contents.\n");
901
	dbg_xfrin_verb("Old contents: %p, apex: %p, new apex: %p\n",
Jan Včelák's avatar
Jan Včelák committed
902 903
		       zone->contents, (zone->contents)
		       ? zone->contents->apex : NULL, new_contents->apex);
904

905
	zone_contents_t *old =
906
		zone_switch_contents(zone, new_contents);
907

908
	dbg_xfrin_verb("Old contents: %p, apex: %p, new apex: %p\n",
Jan Včelák's avatar
Jan Včelák committed
909
		       old, (old) ? old->apex : NULL, new_contents->apex);
910

911 912
	// set generation to old, so that the flags may be used in next transfer
	// and we do not search for new nodes anymore
913
	zone_contents_set_gen_old(new_contents);
914

915 916
	// wait for readers to finish
	dbg_xfrin_verb("Waiting for readers to finish...\n");
917
	synchronize_rcu();
918

919
	return old;
920
}