handlers.c 15.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*  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/>.
*/

17
#include "dnssec/random.h"
18
#include "libknot/libknot.h"
19
#include "libknot/processing/requestor.h"
20
#include "libknot/rrtype/soa.h"
21

22
#include "knot/common/trim.h"
23
#include "libknot/internal/mempool.h"
24
#include "libknot/internal/macros.h"
25

26 27
#include "knot/server/udp-handler.h"
#include "knot/server/tcp-handler.h"
28
#include "knot/updates/changesets.h"
29
#include "knot/dnssec/zone-events.h"
30
#include "knot/zone/timers.h"
31 32
#include "knot/zone/zone-load.h"
#include "knot/zone/zonefile.h"
33
#include "knot/zone/events/events.h"
Jan Kadlec's avatar
Jan Kadlec committed
34
#include "knot/zone/events/handlers.h"
35
#include "knot/updates/apply.h"
36
#include "knot/nameserver/internet.h"
37
#include "knot/nameserver/update.h"
38
#include "knot/nameserver/notify.h"
39
#include "knot/nameserver/tsig_ctx.h"
40
#include "knot/nameserver/process_answer.h"
41

42
#define BOOTSTRAP_RETRY (30) /*!< Interval between AXFR bootstrap retries. */
43
#define BOOTSTRAP_MAXTIME (24*60*60) /*!< Maximum AXFR retry cap of 24 hours. */
44

45 46 47
/* ------------------------- zone query requesting -------------------------- */

/*! \brief Zone event logging. */
Daniel Salzman's avatar
Daniel Salzman committed
48 49
#define ZONE_QUERY_LOG(severity, zone, remote, operation, msg...) \
	NS_PROC_LOG(severity, &remote->addr, zone->name, operation, msg)
50 51

/*! \brief Create zone query packet. */
52
static knot_pkt_t *zone_query(const zone_t *zone, uint16_t pkt_type, mm_ctx_t *mm)
53
{
54 55 56 57 58 59 60 61 62
	/* Determine query type and opcode. */
	uint16_t query_type = KNOT_RRTYPE_SOA;
	uint16_t opcode = KNOT_OPCODE_QUERY;
	switch(pkt_type) {
	case KNOT_QUERY_AXFR: query_type = KNOT_RRTYPE_AXFR; break;
	case KNOT_QUERY_IXFR: query_type = KNOT_RRTYPE_IXFR; break;
	case KNOT_QUERY_NOTIFY: opcode = KNOT_OPCODE_NOTIFY; break;
	}

63 64 65 66 67
	knot_pkt_t *pkt = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, mm);
	if (pkt == NULL) {
		return NULL;
	}

68
	knot_wire_set_id(pkt->wire, dnssec_random_uint16_t());
69
	knot_wire_set_aa(pkt->wire);
70 71 72 73 74 75 76 77
	knot_wire_set_opcode(pkt->wire, opcode);
	knot_pkt_put_question(pkt, zone->name, KNOT_CLASS_IN, query_type);

	/* Put current SOA (optional). */
	zone_contents_t *contents = zone->contents;
	if (pkt_type == KNOT_QUERY_IXFR) {  /* RFC1995, SOA in AUTHORITY. */
		knot_pkt_begin(pkt, KNOT_AUTHORITY);
		knot_rrset_t soa_rr = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
78
		knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &soa_rr, 0);
79 80 81
	} else if (pkt_type == KNOT_QUERY_NOTIFY) { /* RFC1996, SOA in ANSWER. */
		knot_pkt_begin(pkt, KNOT_ANSWER);
		knot_rrset_t soa_rr = node_rrset(contents->apex, KNOT_RRTYPE_SOA);
82
		knot_pkt_put(pkt, KNOT_COMPR_HINT_QNAME, &soa_rr, 0);
83
	}
84

85 86 87 88 89 90 91 92 93 94 95 96 97 98
	return pkt;
}

/*!
 * \brief Create a zone event query, send it, wait for the response and process it.
 *
 * \note Everything in this function is executed synchronously, returns when
 *       the query processing is either complete or an error occurs.
 */
static int zone_query_execute(zone_t *zone, uint16_t pkt_type, const conf_iface_t *remote)
{
	/* Create a memory pool for this task. */
	int ret = KNOT_EOK;
	mm_ctx_t mm;
99
	mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE);
100

101
	/* Create a query message. */
102
	knot_pkt_t *query = zone_query(zone, pkt_type, &mm);
103 104 105
	if (query == NULL) {
		return KNOT_ENOMEM;
	}
106

107 108 109 110 111
	/* Answer processing parameters. */
	struct process_answer_param param = { 0 };
	param.zone = zone;
	param.query = query;
	param.remote = &remote->addr;
112 113 114 115

	/* Create requestor instance. */
	struct knot_requestor re;
	knot_requestor_init(&re, &mm);
116
	knot_requestor_overlay(&re, KNOT_NS_PROC_ANSWER, &param);
117

118 119 120 121
	tsig_init(&param.tsig_ctx, remote->key);

	ret = tsig_sign_packet(&param.tsig_ctx, query);
	if (ret != KNOT_EOK) {
122
		goto fail;
123 124
	}

125
	/* Create a request. */
126 127
	const struct sockaddr *dst = (const struct sockaddr *)&remote->addr;
	const struct sockaddr *src = (const struct sockaddr *)&remote->via;
128
	struct knot_request *req = knot_request_make(re.mm, dst, src, query, 0);
129
	if (req == NULL) {
130 131
		ret = KNOT_ENOMEM;
		goto fail;
132 133
	}

134
	/* Send the queries and process responses. */
135
	ret = knot_requestor_enqueue(&re, req);
136 137
	if (ret == KNOT_EOK) {
		struct timeval tv = { conf()->max_conn_reply, 0 };
138
		ret = knot_requestor_exec(&re, &tv);
139
	}
140

141
fail:
142
	/* Cleanup. */
143
	tsig_cleanup(&param.tsig_ctx);
144
	knot_requestor_clear(&re);
145 146 147
	mp_delete(mm.ctx);

	return ret;
148 149
}

150 151 152
/* @note Module specific, expects some variables set. */
#define ZONE_XFER_LOG(severity, pkt_type, msg...) \
	if (pkt_type == KNOT_QUERY_AXFR) { \
Daniel Salzman's avatar
Daniel Salzman committed
153
		ZONE_QUERY_LOG(severity, zone, master, "AXFR, incoming", msg); \
154
	} else { \
Daniel Salzman's avatar
Daniel Salzman committed
155
		ZONE_QUERY_LOG(severity, zone, master, "IXFR, incoming", msg); \
156 157
	}

158
/*! \brief Execute zone transfer request. */
159 160 161 162 163 164 165 166 167
static int zone_query_transfer(zone_t *zone, const conf_iface_t *master, uint16_t pkt_type)
{
	assert(zone);
	assert(master);

	int ret = zone_query_execute(zone, pkt_type, master);
	if (ret != KNOT_EOK) {
		/* IXFR failed, revert to AXFR. */
		if (pkt_type == KNOT_QUERY_IXFR) {
168
			ZONE_XFER_LOG(LOG_NOTICE, pkt_type, "fallback to AXFR");
169 170 171 172
			return zone_query_transfer(zone, master, KNOT_QUERY_AXFR);
		}

		/* Log connection errors. */
173
		ZONE_XFER_LOG(LOG_ERR, pkt_type, "failed (%s)", knot_strerror(ret));
174 175 176 177 178 179 180
	}

	return ret;
}

#undef ZONE_XFER_LOG

181 182 183 184 185 186 187 188 189 190 191
/*!
 * \todo Separate signing from zone loading and drop this function.
 *
 * DNSSEC signing is planned from two places - after zone loading and after
 * successful resign. This function just logs the message and reschedules the
 * DNSSEC timer.
 *
 * I would rather see the invocation of the signing from event_dnssec()
 * function. This would require to split refresh event to zone load and zone
 * publishing.
 */
192
static void schedule_dnssec(zone_t *zone, time_t refresh_at)
193 194 195 196 197
{
	// log a message

	char time_str[64] = { 0 };
	struct tm time_gm = { 0 };
198
	localtime_r(&refresh_at, &time_gm);
199
	strftime(time_str, sizeof(time_str), KNOT_LOG_TIME_FORMAT, &time_gm);
200
	log_zone_info(zone->name, "DNSSEC, next signing on %s", time_str);
201 202 203

	// schedule

204
	zone_events_schedule_at(zone, ZONE_EVENT_DNSSEC, refresh_at);
205 206
}

207 208 209 210 211
/*! \brief Get SOA from zone. */
static const knot_rdataset_t *zone_soa(zone_t *zone)
{
	return node_rdataset(zone->contents->apex, KNOT_RRTYPE_SOA);
}
212

213 214 215 216 217
/*! \brief Fetch SOA expire timer and add a timeout grace period. */
static uint32_t soa_graceful_expire(const knot_rdataset_t *soa)
{
	// Allow for timeouts.  Otherwise zones with very short
	// expiry may expire before the timeout is reached.
218
	return knot_soa_expire(soa) + 2 * conf()->max_conn_idle;
219 220
}

221 222 223 224 225 226 227 228 229 230 231 232
/*! \brief Schedule expire event, unless it is already scheduled. */
static void start_expire_timer(zone_t *zone, const knot_rdataset_t *soa)
{
	if (zone_events_is_scheduled(zone, ZONE_EVENT_EXPIRE)) {
		return;
	}

	zone_events_schedule(zone, ZONE_EVENT_EXPIRE, soa_graceful_expire(soa));
}

/* -- zone events handling callbacks --------------------------------------- */

233
int event_reload(zone_t *zone)
234 235 236
{
	assert(zone);

237 238
	/* Take zone file mtime and load it. */
	time_t mtime = zonefile_mtime(zone->conf->file);
239
	uint32_t dnssec_refresh = time(NULL);
240 241 242
	conf_zone_t *zone_config = zone->conf;
	zone_contents_t *contents = zone_load_contents(zone_config);
	if (!contents) {
243
		return KNOT_ERROR;
244 245
	}

246 247
	/* Store zonefile serial and apply changes from the journal. */
	zone->zonefile_serial = zone_contents_serial(contents);
248
	int result = zone_load_journal(zone, contents);
249
	if (result != KNOT_EOK) {
250
		goto fail;
251 252
	}

253
	/* Post load actions - calculate delta, sign with DNSSEC... */
254 255
	/*! \todo issue #242 dnssec signing should occur in the special event */
	result = zone_load_post(contents, zone, &dnssec_refresh);
256 257
	if (result != KNOT_EOK) {
		if (result == KNOT_ESPACE) {
258
			log_zone_error(zone->name, "journal size is too small "
259
			               "to fit the changes");
260
		} else {
261
			log_zone_error(zone->name, "failed to store changes into "
262
			               "journal (%s)", knot_strerror(result));
263
		}
264
		goto fail;
265
	}
266

267 268 269 270 271 272 273 274 275
	/* Check zone contents consistency. */
	result = zone_load_check(contents, zone_config);
	if (result != KNOT_EOK) {
		goto fail;
	}

	/* Everything went alright, switch the contents. */
	zone->zonefile_mtime = mtime;
	zone_contents_t *old = zone_switch_contents(zone, contents);
276
	uint32_t old_serial = zone_contents_serial(old);
277 278 279 280 281
	if (old != NULL) {
		synchronize_rcu();
		zone_contents_deep_free(&old);
	}

282
	/* Schedule notify and refresh after load. */
283
	if (zone_master(zone)) {
284
		zone_events_schedule(zone, ZONE_EVENT_REFRESH, ZONE_EVENT_NOW);
285
	}
286
	if (!zone_contents_is_empty(contents)) {
Marek Vavruša's avatar
Marek Vavruša committed
287
		zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
288
		zone->bootstrap_retry = ZONE_EVENT_NOW;
289
	}
290 291

	/* Schedule zone resign. */
292
	if (zone->conf->dnssec_enable) {
293
		schedule_dnssec(zone, dnssec_refresh);
294 295 296 297
	}

	/* Periodic execution. */
	zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone_config->dbsync_timeout);
298

299
	uint32_t current_serial = zone_contents_serial(zone->contents);
300
	log_zone_info(zone->name, "loaded, serial %u -> %u",
301
	              old_serial, current_serial);
302

303
	return zone_events_write_persistent(zone);
304 305 306 307

fail:
	zone_contents_deep_free(&contents);
	return result;
308 309
}

310
int event_refresh(zone_t *zone)
311 312
{
	assert(zone);
313

314 315 316 317 318 319
	const conf_iface_t *master = zone_master(zone);
	if (master == NULL) {
		/* If not slave zone, ignore. */
		return KNOT_EOK;
	}

320
	if (zone_contents_is_empty(zone->contents)) {
321
		/* No contents, schedule retransfer now. */
322
		zone_events_schedule(zone, ZONE_EVENT_XFER, ZONE_EVENT_NOW);
323 324 325
		return KNOT_EOK;
	}

326
	int ret = zone_query_execute(zone, KNOT_QUERY_NORMAL, master);
327
	const knot_rdataset_t *soa = zone_soa(zone);
328
	if (ret != KNOT_EOK) {
329
		/* Log connection errors. */
330 331
		ZONE_QUERY_LOG(LOG_WARNING, zone, master, "SOA query, outgoing",
		               "failed (%s)", knot_strerror(ret));
332 333 334
		/* Rotate masters if current failed. */
		zone_master_rotate(zone);
		/* Schedule next retry. */
335
		zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_retry(soa));
336
		start_expire_timer(zone, soa);
337
	} else {
338
		/* SOA query answered, reschedule refresh timer. */
339
		zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
340
	}
341

342
	return zone_events_write_persistent(zone);
343
}
344

345
int event_xfer(zone_t *zone)
346 347
{
	assert(zone);
348

349 350 351 352 353 354
	const conf_iface_t *master = zone_master(zone);
	if (master == NULL) {
		/* If not slave zone, ignore. */
		return KNOT_EOK;
	}

355
	/* Determine transfer type. */
356
	bool is_boostrap = zone_contents_is_empty(zone->contents);
357
	uint16_t pkt_type = KNOT_QUERY_IXFR;
358
	if (is_boostrap || zone->flags & ZONE_FORCE_AXFR) {
359
		pkt_type = KNOT_QUERY_AXFR;
360 361
	}

362
	/* Execute zone transfer and reschedule timers. */
363
	int ret = zone_query_transfer(zone, master, pkt_type);
364 365 366 367 368 369 370

	/* Handle failure during transfer. */
	if (ret != KNOT_EOK) {
		if (is_boostrap) {
			zone->bootstrap_retry = bootstrap_next(zone->bootstrap_retry);
			zone_events_schedule(zone, ZONE_EVENT_XFER, zone->bootstrap_retry);
		} else {
371 372 373
			const knot_rdataset_t *soa = zone_soa(zone);
			zone_events_schedule(zone, ZONE_EVENT_XFER, knot_soa_retry(soa));
			start_expire_timer(zone, soa);
374
		}
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396

		return KNOT_EOK;
	}

	assert(!zone_contents_is_empty(zone->contents));
	const knot_rdataset_t *soa = zone_soa(zone);

	/* Rechedule events. */
	zone_events_schedule(zone, ZONE_EVENT_REFRESH, knot_soa_refresh(soa));
	zone_events_schedule(zone, ZONE_EVENT_NOTIFY,  ZONE_EVENT_NOW);
	zone_events_cancel(zone, ZONE_EVENT_EXPIRE);
	if (zone->conf->dbsync_timeout == 0) {
		zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
	} else if (!zone_events_is_scheduled(zone, ZONE_EVENT_FLUSH)) {
		zone_events_schedule(zone, ZONE_EVENT_FLUSH, zone->conf->dbsync_timeout);
	}

	/* Transfer cleanup. */
	zone->bootstrap_retry = ZONE_EVENT_NOW;
	zone->flags &= ~ZONE_FORCE_AXFR;

	/* Trim extra heap. */
397
	if (!is_boostrap) {
398
		mem_trim();
399 400
	}

401
	return KNOT_EOK;
402 403
}

404
int event_update(zone_t *zone)
405 406 407
{
	assert(zone);

408 409
	/* Process update list - forward if zone has master, or execute. */
	int ret = updates_execute(zone);
410
	UNUSED(ret); /* Don't care about the Knot code, RCODEs are set. */
411

412 413 414
	/* Trim extra heap. */
	mem_trim();

Jan Kadlec's avatar
Jan Kadlec committed
415
	/* Replan event if next update waiting. */
416
	pthread_mutex_lock(&zone->ddns_lock);
417

418
	const bool empty = EMPTY_LIST(zone->ddns_queue);
419

420
	pthread_mutex_unlock(&zone->ddns_lock);
421

422
	if (!empty) {
Jan Kadlec's avatar
Jan Kadlec committed
423 424 425
		zone_events_schedule(zone, ZONE_EVENT_UPDATE, ZONE_EVENT_NOW);
	}

426
	return KNOT_EOK;
427 428
}

429
int event_expire(zone_t *zone)
430 431 432
{
	assert(zone);

433 434
	zone_contents_t *expired = zone_switch_contents(zone, NULL);
	synchronize_rcu();
435 436 437 438

	/* Expire zonefile information. */
	zone->zonefile_mtime = 0;
	zone->zonefile_serial = 0;
439 440
	zone_contents_deep_free(&expired);

441
	log_zone_info(zone->name, "zone expired");
442

443 444 445
	/* Trim extra heap. */
	mem_trim();

446 447 448
	return KNOT_EOK;
}

449
int event_flush(zone_t *zone)
450
{
451
	assert(zone);
452 453 454 455

	/* Reschedule. */
	int next_timeout = zone->conf->dbsync_timeout;
	if (next_timeout > 0) {
456
		zone_events_schedule(zone, ZONE_EVENT_FLUSH, next_timeout);
457
	}
458

459
	/* Check zone contents. */
460 461 462 463
	if (zone_contents_is_empty(zone->contents)) {
		return KNOT_EOK;
	}

464
	return zone_flush_journal(zone);
465 466
}

467
int event_notify(zone_t *zone)
468 469 470
{
	assert(zone);

471 472 473 474 475
	/* Check zone contents. */
	if (zone_contents_is_empty(zone->contents)) {
		return KNOT_EOK;
	}

476 477 478 479 480
	/* Walk through configured remotes and send messages. */
	conf_remote_t *remote = 0;
	WALK_LIST(remote, zone->conf->acl.notify_out) {
		conf_iface_t *iface = remote->remote;

481
		int ret = zone_query_execute(zone, KNOT_QUERY_NOTIFY, iface);
482
		if (ret == KNOT_EOK) {
Daniel Salzman's avatar
Daniel Salzman committed
483
			ZONE_QUERY_LOG(LOG_INFO, zone, iface, "NOTIFY, outgoing",
484
			               "serial %u",
485 486
			               zone_contents_serial(zone->contents));
		} else {
Daniel Salzman's avatar
Daniel Salzman committed
487
			ZONE_QUERY_LOG(LOG_WARNING, zone, iface, "NOTIFY, outgoing",
488
			               "failed (%s)", knot_strerror(ret));
489
		}
490 491
	}

492
	return KNOT_EOK;
493 494
}

495
int event_dnssec(zone_t *zone)
496 497 498
{
	assert(zone);

499
	changeset_t ch;
500
	int ret = changeset_init(&ch, zone->name);
501 502 503 504
	if (ret != KNOT_EOK) {
		goto done;
	}

505
	uint32_t refresh_at = time(NULL);
506 507
	int sign_flags = 0;

508
	if (zone->flags & ZONE_FORCE_RESIGN) {
509
		log_zone_info(zone->name, "DNSSEC, dropping previous "
510
		              "signatures, resigning zone");
511
		zone->flags &= ~ZONE_FORCE_RESIGN;
512
		sign_flags = ZONE_SIGN_DROP_SIGNATURES;
513
	} else {
514
		log_zone_info(zone->name, "DNSSEC, signing zone");
515
		sign_flags = 0;
516
	}
517 518

	ret = knot_dnssec_zone_sign(zone->contents, zone->conf, &ch, sign_flags, &refresh_at);
519 520 521 522
	if (ret != KNOT_EOK) {
		goto done;
	}

523
	if (!changeset_empty(&ch)) {
524
		/* Apply change. */
525
		zone_contents_t *new_contents = NULL;
526
		int ret = apply_changeset(zone, &ch, &new_contents);
527
		if (ret != KNOT_EOK) {
528
			log_zone_error(zone->name, "DNSSEC, failed to sign zone (%s)",
529
				       knot_strerror(ret));
530 531
			goto done;
		}
532 533 534 535

		/* Write change to journal. */
		ret = zone_change_store(zone, &ch);
		if (ret != KNOT_EOK) {
536
			log_zone_error(zone->name, "DNSSEC, failed to sign zone (%s)",
537
				       knot_strerror(ret));
538
			update_rollback(&ch);
539
			update_free_zone(&new_contents);
540 541 542 543 544 545
			goto done;
		}

		/* Switch zone contents. */
		zone_contents_t *old_contents = zone_switch_contents(zone, new_contents);
		synchronize_rcu();
546
		update_free_zone(&old_contents);
547 548

		update_cleanup(&ch);
549 550
	}

551 552
	// Schedule dependent events.

553
	schedule_dnssec(zone, refresh_at);
554 555 556 557
	zone_events_schedule(zone, ZONE_EVENT_NOTIFY, ZONE_EVENT_NOW);
	if (zone->conf->dbsync_timeout == 0) {
		zone_events_schedule(zone, ZONE_EVENT_FLUSH, ZONE_EVENT_NOW);
	}
558 559

done:
560
	changeset_clear(&ch);
561
	return ret;
562 563
}

564 565
#undef ZONE_QUERY_LOG

566 567
/*! \brief Progressive bootstrap retry timer. */
uint32_t bootstrap_next(uint32_t timer)
568
{
569
	timer *= 2;
570
	timer += dnssec_random_uint32_t() % BOOTSTRAP_RETRY;
571 572
	if (timer > BOOTSTRAP_MAXTIME) {
		timer = BOOTSTRAP_MAXTIME;
573
	}
574
	return timer;
575 576
}