zone-update.c 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*  Copyright (C) 2014 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 <http://www.gnu.org/licenses/>.
 */

#include "knot/updates/zone-update.h"
18 19

#include "knot/common/log.h"
20 21
#include "knot/dnssec/zone-events.h"
#include "knot/updates/apply.h"
22 23 24 25 26
#include "knot/zone/serial.h"

#include "libknot/internal/lists.h"
#include "libknot/internal/mempool.h"

27
static int add_to_node(zone_node_t *node, const zone_node_t *add_node,
28 29 30 31 32
                       mm_ctx_t *mm)
{
	for (uint16_t i = 0; i < add_node->rrset_count; ++i) {
		knot_rrset_t rr = node_rrset_at(add_node, i);
		if (!knot_rrset_empty(&rr)) {
33
			int ret = node_add_rrset(node, &rr, mm);
34 35 36 37 38 39 40 41 42
			if (ret != KNOT_EOK) {
				return ret;
			}
		}
	}

	return KNOT_EOK;
}

43
static int rem_from_node(zone_node_t *node, const zone_node_t *rem_node,
44 45 46
                         mm_ctx_t *mm)
{
	for (uint16_t i = 0; i < rem_node->rrset_count; ++i) {
47
		// Remove each found RR from 'node'.
48
		knot_rrset_t rem_rrset = node_rrset_at(rem_node, i);
49
		knot_rdataset_t *to_change = node_rdataset(node, rem_rrset.type);
50
		if (to_change) {
51
			// Remove data from synthesized node
52
			int ret = knot_rdataset_subtract(to_change,
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
			                                 &rem_rrset.rrs,
			                                 mm);
			if (ret != KNOT_EOK) {
				return ret;
			}
		}
	}

	return KNOT_EOK;
}

static int apply_changes_to_node(zone_node_t *synth_node, const zone_node_t *add_node,
                                 const zone_node_t *rem_node, mm_ctx_t *mm)
{
	// Add changes to node
	if (!node_empty(add_node)) {
		int ret = add_to_node(synth_node, add_node, mm);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	// Remove changes from node
	if (!node_empty(rem_node)) {
		int ret = rem_from_node(synth_node, rem_node, mm);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	return KNOT_EOK;
}

86
static int deep_copy_node_data(zone_node_t *node_copy, const zone_node_t *node,
87 88
                               mm_ctx_t *mm)
{
89 90 91
	// Clear space for RRs
	node_copy->rrs = NULL;
	node_copy->rrset_count = 0;
92

93 94
	for (uint16_t i = 0; i < node->rrset_count; ++i) {
		knot_rrset_t rr = node_rrset_at(node, i);
95
		int ret = node_add_rrset(node_copy, &rr, mm);
96 97 98 99 100 101 102 103
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	return KNOT_EOK;
}

104
static zone_node_t *node_deep_copy(const zone_node_t *node, mm_ctx_t *mm)
105 106
{
	// Shallow copy old node
107
	zone_node_t *synth_node = node_shallow_copy(node, mm);
108 109 110 111 112
	if (synth_node == NULL) {
		return NULL;
	}

	// Deep copy data inside node copy.
113
	int ret = deep_copy_node_data(synth_node, node, mm);
114 115 116 117 118 119 120 121
	if (ret != KNOT_EOK) {
		node_free(&synth_node, mm);
		return NULL;
	}

	return synth_node;
}

122 123 124 125 126 127 128 129
static int init_incremental(zone_update_t *update, zone_t *zone)
{
	int ret = changeset_init(&update->change, zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}
	assert(zone->contents);

130 131 132 133 134 135
	// Copy base SOA RR.
	update->change.soa_from =
		node_create_rrset(update->zone->contents->apex, KNOT_RRTYPE_SOA);
	if (update->change.soa_from == NULL) {
		return KNOT_ENOMEM;
	}
136 137 138 139 140 141 142 143 144 145 146 147 148 149

	return KNOT_EOK;
}

static int init_full(zone_update_t *update, zone_t *zone)
{
	update->new_cont = zone_contents_new(zone->name);
	if (update->new_cont == NULL) {
		return KNOT_ENOMEM;
	}

	return KNOT_EOK;
}

150 151
/* ------------------------------- API -------------------------------------- */

152
int zone_update_init(zone_update_t *update, zone_t *zone, zone_update_flags_t flags)
153
{
154 155 156 157 158
	if (update == NULL || zone == NULL) {
		return KNOT_EINVAL;
	}

	memset(update, 0, sizeof(*update));
159
	update->zone = zone;
160

161
	mm_ctx_mempool(&update->mm, MM_DEFAULT_BLKSIZE);
162 163 164 165 166 167 168 169 170
	update->flags = flags;

	if (flags & UPDATE_INCREMENTAL) {
		return init_incremental(update, zone);
	} else if (flags & UPDATE_FULL) {
		return init_full(update, zone);
	} else {
		return KNOT_EINVAL;
	}
171 172 173 174 175 176 177 178
}

const zone_node_t *zone_update_get_node(zone_update_t *update, const knot_dname_t *dname)
{
	if (update == NULL || dname == NULL) {
		return NULL;
	}

179
	const zone_node_t *old_node =
180
		zone_contents_find_node(update->zone->contents, dname);
181
	const zone_node_t *add_node =
182
		zone_contents_find_node(update->change.add, dname);
183
	const zone_node_t *rem_node =
184
		zone_contents_find_node(update->change.remove, dname);
185 186 187

	const bool have_change = !node_empty(add_node) || !node_empty(rem_node);
	if (!have_change) {
188
		// Nothing to apply
189 190 191
		return old_node;
	}

192
	if (!old_node) {
193
		if (add_node && node_empty(rem_node)) {
194
			// Just addition
195 196
			return add_node;
		} else {
197 198 199
			// Addition and deletion
			old_node = add_node;
			add_node = NULL;
200 201
		}
	}
202 203 204 205 206 207 208 209 210 211 212

	// We have to apply changes to node.
	zone_node_t *synth_node = node_deep_copy(old_node, &update->mm);
	if (synth_node == NULL) {
		return NULL;
	}

	// Apply changes to node.
	int ret = apply_changes_to_node(synth_node, add_node, rem_node,
	                                &update->mm);
	if (ret != KNOT_EOK) {
213
		node_free_rrsets(synth_node, &update->mm);
Jan Kadlec's avatar
Jan Kadlec committed
214
		node_free(&synth_node, &update->mm);
215 216 217 218
		return NULL;
	}

	return synth_node;
219 220
}

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
const zone_node_t *zone_update_get_apex(zone_update_t *update)
{
	return zone_update_get_node(update, update->zone->name);
}

uint32_t zone_update_current_serial(zone_update_t *update)
{
	const zone_node_t *apex = zone_update_get_apex(update);
	if (apex) {
		return knot_soa_serial(node_rdataset(apex, KNOT_RRTYPE_SOA));
	} else {
		return 0;
	}
}

const knot_rdataset_t *zone_update_from(zone_update_t *update)
{
	const zone_node_t *apex = update->zone->contents->apex;
	return node_rdataset(apex, KNOT_RRTYPE_SOA);
}

const knot_rdataset_t *zone_update_to(zone_update_t *update)
{
	assert(update);

	if (update->change.soa_to == NULL) {
		return NULL;
	}

	return &update->change.soa_to->rrs;
}

253 254
void zone_update_clear(zone_update_t *update)
{
255
	if (update) {
256
		changeset_clear(&update->change);
257
		mp_delete(update->mm.ctx);
Jan Kadlec's avatar
Jan Kadlec committed
258
		memset(update, 0, sizeof(*update));
259
	}
260
}
261

262 263
int zone_update_add(zone_update_t *update, const knot_rrset_t *rrset)
{
264
	if (update->flags & UPDATE_INCREMENTAL) {
265 266 267 268 269 270 271 272 273 274 275
		return changeset_add_rrset(&update->change, rrset);
	} else if (update->flags & UPDATE_FULL) {
		zone_node_t *n = NULL;
		return zone_contents_add_rr(update->new_cont, rrset, &n);
	} else {
		return KNOT_EINVAL;
	}
}

int zone_update_remove(zone_update_t *update, const knot_rrset_t *rrset)
{
276
	if (update->flags & UPDATE_INCREMENTAL) {
277 278 279 280 281 282
		return changeset_rem_rrset(&update->change, rrset);
	} else {
		return KNOT_ENOTSUP;
	}
}

283 284 285 286 287 288 289 290 291 292
static bool apex_rr_changed(const zone_node_t *old_apex,
                            const zone_node_t *new_apex,
                            uint16_t type)
{
	knot_rrset_t old_rr = node_rrset(old_apex, type);
	knot_rrset_t new_rr = node_rrset(new_apex, type);

	return !knot_rrset_equal(&old_rr, &new_rr, KNOT_RRSET_COMPARE_WHOLE);
}

293
static bool apex_dnssec_changed(zone_update_t *update)
294 295 296 297 298 299 300 301 302 303
{
	assert(update->zone->contents);
	const zone_node_t *new_apex = zone_update_get_apex(update);
	const zone_node_t *old_apex = update->zone->contents->apex;
	return !changeset_empty(&update->change) &&
	       (apex_rr_changed(new_apex, old_apex, KNOT_RRTYPE_DNSKEY) ||
	        apex_rr_changed(new_apex, old_apex, KNOT_RRTYPE_NSEC3PARAM));
}

static int sign_update(zone_update_t *update,
304
                       zone_contents_t *new_contents)
305 306 307 308 309 310 311 312 313
{
	assert(update != NULL);

	/*
	 * Check if the UPDATE changed DNSKEYs or NSEC3PARAM.
	 * If so, we have to sign the whole zone.
	 */
	int ret = KNOT_EOK;
	uint32_t refresh_at = 0;
314 315 316 317 318 319
	changeset_t sec_ch;
	ret = changeset_init(&sec_ch, update->zone->name);
	if (ret != KNOT_EOK) {
		return ret;
	}

320
	const bool full_sign = changeset_empty(&update->change) ||
321
	                       apex_dnssec_changed(update);
322
	if (full_sign) {
323
		ret = knot_dnssec_zone_sign(new_contents, &sec_ch,
324 325 326 327 328
		                            ZONE_SIGN_KEEP_SOA_SERIAL,
		                            &refresh_at);
	} else {
		// Sign the created changeset
		ret = knot_dnssec_sign_changeset(new_contents, &update->change,
329
		                                 &sec_ch, &refresh_at);
330 331
	}
	if (ret != KNOT_EOK) {
332
		changeset_clear(&sec_ch);
333 334 335 336
		return ret;
	}

	// Apply DNSSEC changeset
337
	ret = apply_changeset_directly(new_contents, &sec_ch);
338
	if (ret != KNOT_EOK) {
339
		changeset_clear(&sec_ch);
340 341 342 343
		return ret;
	}

	// Merge changesets
344
	ret = changeset_merge(&update->change, &sec_ch);
345
	if (ret != KNOT_EOK) {
346 347
		update_rollback(&sec_ch);
		changeset_clear(&sec_ch);
348 349 350 351 352 353 354 355 356
		return ret;
	}

	// Plan next zone resign.
	const time_t resign_time = zone_events_get_time(update->zone, ZONE_EVENT_DNSSEC);
	if (refresh_at < resign_time) {
		zone_events_schedule_at(update->zone, ZONE_EVENT_DNSSEC, refresh_at);
	}

357 358 359 360 361 362
	/*
	 * We are not calling update_cleanup, as the rollback data are merged
	 * into the main changeset and will get cleaned up with that.
	 */
	changeset_clear(&sec_ch);

363 364 365
	return KNOT_EOK;
}

366 367
static int set_new_soa(zone_update_t *update)
{
368 369
	assert(update);

370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	knot_rrset_t *soa_cpy = node_create_rrset(zone_update_get_apex(update), KNOT_RRTYPE_SOA);
	if (soa_cpy == NULL) {
		return KNOT_ENOMEM;
	}

	conf_val_t val = conf_zone_get(conf(), C_SERIAL_POLICY, update->zone->name);
	uint32_t old_serial = knot_soa_serial(&soa_cpy->rrs);
	uint32_t new_serial = serial_next(old_serial, conf_opt(&val));
	if (serial_compare(old_serial, new_serial) >= 0) {
		log_zone_warning(update->zone->name, "updated serial is lower "
		                 "than current, serial %u -> %u",
		                  old_serial, new_serial);
	}

	knot_soa_serial_set(&soa_cpy->rrs, new_serial);
	update->change.soa_to = soa_cpy;

	return KNOT_EOK;
}

390
static int commit_incremental(zone_update_t *update, zone_contents_t **contents_out)
391
{
392 393
	assert(update);

394 395 396 397 398
	if (changeset_empty(&update->change)) {
		changeset_clear(&update->change);
		return KNOT_EOK;
	}

399
	int ret = KNOT_EOK;
400 401 402 403 404 405 406 407 408
	if (zone_update_to(update) == NULL) {
		// No SOA in the update, create one according to the current policy
		ret = set_new_soa(update);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}

	// Apply changes.
409
	zone_contents_t *new_contents = NULL;
410 411 412 413 414
	ret = apply_changeset(update->zone, &update->change, &new_contents);
	if (ret != KNOT_EOK) {
		changeset_clear(&update->change);
		return ret;
	}
415

416
	assert(new_contents);
417

418 419
	conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, update->zone->name);
	bool dnssec_enable = update->flags & UPDATE_SIGN && conf_bool(&val);
420

421 422
	// Sign the update.
	if (dnssec_enable) {
423
		ret = sign_update(update, new_contents);
424 425 426
		if (ret != KNOT_EOK) {
			update_rollback(&update->change);
			update_free_zone(&new_contents);
427
			changeset_clear(&update->change);
428 429
			return ret;
		}
430
	}
431

432 433 434 435 436 437 438
	// Write changes to journal if all went well. (DNSSEC merged)
	ret = zone_change_store(update->zone, &update->change);
	if (ret != KNOT_EOK) {
		update_rollback(&update->change);
		update_free_zone(&new_contents);
		return ret;
	}
439

440 441
	update_cleanup(&update->change);
	changeset_clear(&update->change);
442

443 444
	*contents_out = new_contents;

445 446 447
	return KNOT_EOK;
}

448
int zone_update_commit(zone_update_t *update, zone_contents_t **contents_out)
449 450
{
	if (update->flags & UPDATE_INCREMENTAL) {
451
		return commit_incremental(update, contents_out);
452 453 454 455 456
	}

	return KNOT_ENOTSUP;
}

457 458 459 460
bool zone_update_no_change(zone_update_t *up)
{
	return changeset_empty(&up->change);
}