ixfr.c 11.6 KB
Newer Older
1 2 3
#include "knot/nameserver/ixfr.h"
#include "knot/nameserver/axfr.h"
#include "knot/nameserver/internet.h"
4
#include "knot/nameserver/process_query.h"
5
#include "knot/nameserver/process_answer.h"
6
#include "knot/updates/xfr-in.h"
7
#include "common/debug.h"
8
#include "common/descriptor.h"
9
#include "libknot/util/utils.h"
10
#include "libknot/rdata/soa.h"
11

12 13 14 15 16 17 18 19
/*! \brief Current IXFR answer sections. */
enum {
	SOA_REMOVE = 0,
	REMOVE,
	SOA_ADD,
	ADD
};

20 21
/*! \brief Extended structure for IXFR-out processing. */
struct ixfrout_proc {
22 23 24 25
	struct xfr_proc proc;
	node_t *cur;
	unsigned state;
	knot_changesets_t *changesets;
26
	struct query_data *qdata;
27
	const knot_rrset_t *soa_from, *soa_to;
28 29
};

30
/* IXFR-specific logging (internal, expects 'qdata' variable set). */
31
#define IXFROUT_LOG(severity, msg...) \
32
	QUERY_LOG(severity, qdata, "Outgoing IXFR", msg)
33

34 35 36
#define IXFRIN_LOG(severity, msg...) \
	ANSWER_LOG(severity, adata, "Incoming IXFR", msg)

37 38
/*! \brief Helper macro for putting RRs into packet. */
#define IXFR_SAFE_PUT(pkt, rr) \
39
	ret = knot_pkt_put((pkt), 0, (rr), KNOT_PF_NOTRUNC); \
40 41 42 43
	if (ret != KNOT_EOK) { \
		return ret; \
	}

44
static int ixfr_put_rrlist(knot_pkt_t *pkt, struct ixfrout_proc *ixfr, list_t *list)
45
{
46 47 48 49 50 51 52 53 54 55 56 57
	assert(pkt);
	assert(ixfr);
	assert(list);

	/* If at the beginning, fetch first RR. */
	int ret = KNOT_EOK;
	if (ixfr->cur == NULL) {
		ixfr->cur = HEAD(*list);
	}
	/* Now iterate until it hits the last one,
	 * this is done without for() loop because we can
	 * rejoin the iteration at any point. */
58 59
	while(ixfr->cur->next) {
		knot_rr_ln_t *rr_item = (knot_rr_ln_t *)(ixfr->cur);
60
		if (rr_item->rr->rrs.rr_count > 0) {
61 62 63 64 65 66 67
			IXFR_SAFE_PUT(pkt, rr_item->rr);
		} else {
			dbg_ns("%s: empty RR %p, skipping\n", __func__, rr_item->rr);
		}

		ixfr->cur = ixfr->cur->next;
	}
68 69

	ixfr->cur = NULL;
70
	return ret;
71 72
}

73 74 75 76 77 78 79
/*!
 * \brief Process single changeset.
 * \note Keep in mind that this function must be able to resume processing,
 *       for example if it fills a packet and returns ESPACE, it is called again
 *       with next empty answer and it must resume the processing exactly where
 *       it's left off.
 */
80
static int ixfr_process_changeset(knot_pkt_t *pkt, const void *item, struct xfr_proc *xfer)
81
{
82
	int ret = KNOT_EOK;
83
	struct ixfrout_proc *ixfr = (struct ixfrout_proc *)xfer;
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	knot_changeset_t *chgset = (knot_changeset_t *)item;

	/* Put former SOA. */
	if (ixfr->state == SOA_REMOVE) {
		IXFR_SAFE_PUT(pkt, chgset->soa_from);
		dbg_ns("%s: put 'REMOVE' SOA\n", __func__);
		ixfr->state = REMOVE;
	}

	/* Put REMOVE RRSets. */
	if (ixfr->state == REMOVE) {
		ret = ixfr_put_rrlist(pkt, ixfr, &chgset->remove);
		if (ret != KNOT_EOK) {
			return ret;
		}
		dbg_ns("%s: put 'REMOVE' RRs\n", __func__);
		ixfr->state = SOA_ADD;
	}

	/* Put next SOA. */
	if (ixfr->state == SOA_ADD) {
		IXFR_SAFE_PUT(pkt, chgset->soa_to);
		dbg_ns("%s: put 'ADD' SOA\n", __func__);
		ixfr->state = ADD;
108 109
	}

110 111 112 113 114 115 116 117 118
	/* Put REMOVE RRSets. */
	if (ixfr->state == ADD) {
		ret = ixfr_put_rrlist(pkt, ixfr, &chgset->add);
		if (ret != KNOT_EOK) {
			return ret;
		}
		dbg_ns("%s: put 'ADD' RRs\n", __func__);
		ixfr->state = SOA_REMOVE;
	}
119

120
	/* Finished change set. */
121 122
	struct query_data *qdata = ixfr->qdata; /*< Required for IXFROUT_LOG() */
	IXFROUT_LOG(LOG_INFO, "Serial %u -> %u.", chgset->serial_from, chgset->serial_to);
123 124 125 126 127 128

	return ret;
}

#undef IXFR_SAFE_PUT

129
static int ixfr_load_chsets(knot_changesets_t **chgsets, const zone_t *zone,
130 131 132 133 134 135
			    const knot_rrset_t *their_soa)
{
	assert(chgsets);
	assert(zone);

	/* Compare serials. */
136
	uint32_t serial_to = zone_contents_serial(zone->contents);
137
	uint32_t serial_from = knot_soa_serial(&their_soa->rrs);
138
	int ret = knot_serial_compare(serial_to, serial_from);
139 140 141 142
	if (ret <= 0) { /* We have older/same age zone. */
		return KNOT_EUPTODATE;
	}

143
	*chgsets = knot_changesets_create(0);
144 145 146 147
	if (*chgsets == NULL) {
		return KNOT_ENOMEM;
	}

148 149
	ret = journal_load_changesets(zone->conf->ixfr_db, *chgsets,
	                              serial_from, serial_to);
150
	if (ret != KNOT_EOK) {
151
		knot_changesets_free(chgsets, NULL);
152 153 154 155 156
	}

	return ret;
}

157
static int ixfr_query_check(struct query_data *qdata)
158
{
159 160 161
	/* Check if zone exists. */
	NS_NEED_ZONE(qdata, KNOT_RCODE_NOTAUTH);

162 163 164
	/* Need IXFR query type. */
	NS_NEED_QTYPE(qdata, KNOT_RRTYPE_IXFR, KNOT_RCODE_FORMERR);
	/* Need SOA authority record. */
165
	const knot_pktsection_t *authority = knot_pkt_section(qdata->query, KNOT_AUTHORITY);
166
	const knot_rrset_t *their_soa = &authority->rr[0];
167
	if (authority->count < 1 || their_soa->type != KNOT_RRTYPE_SOA) {
168
		qdata->rcode = KNOT_RCODE_FORMERR;
169
		return NS_PROC_FAIL;
170 171 172
	}
	/* SOA needs to match QNAME. */
	NS_NEED_QNAME(qdata, their_soa->owner, KNOT_RCODE_FORMERR);
173

174
	/* Check transcation security and zone contents. */
175
	NS_NEED_AUTH(qdata->zone->xfr_out, qdata);
176
	NS_NEED_ZONE_CONTENTS(qdata, KNOT_RCODE_SERVFAIL); /* Check expiration. */
177

178
	return NS_PROC_DONE;
179
}
180

181 182
static void ixfr_answer_cleanup(struct query_data *qdata)
{
183
	struct ixfrout_proc *ixfr = (struct ixfrout_proc *)qdata->ext;
184 185 186
	mm_ctx_t *mm = qdata->mm;

	ptrlist_free(&ixfr->proc.nodes, mm);
187
	knot_changesets_free(&ixfr->changesets, NULL);
188
	mm->free(qdata->ext);
189 190 191

	/* Allow zone changes (finished). */
	rcu_read_unlock();
192 193
}

194 195
static int ixfr_answer_init(struct query_data *qdata)
{
196
	/* Check IXFR query validity. */
197 198
	int state = ixfr_query_check(qdata);
	if (state == NS_PROC_FAIL) {
199 200 201 202 203
		if (qdata->rcode == KNOT_RCODE_FORMERR) {
			return KNOT_EMALF;
		} else {
			return KNOT_EDENIED;
		}
204
	}
205

206
	/* Compare serials. */
207
	const knot_rrset_t *their_soa = &knot_pkt_section(qdata->query, KNOT_AUTHORITY)->rr[0];
208 209 210
	knot_changesets_t *chgsets = NULL;
	int ret = ixfr_load_chsets(&chgsets, qdata->zone, their_soa);
	if (ret != KNOT_EOK) {
211
		dbg_ns("%s: failed to load changesets => %d\n", __func__, ret);
212 213 214 215 216
		return ret;
	}

	/* Initialize transfer processing. */
	mm_ctx_t *mm = qdata->mm;
217
	struct ixfrout_proc *xfer = mm->alloc(mm->ctx, sizeof(struct ixfrout_proc));
218
	if (xfer == NULL) {
219
		knot_changesets_free(&chgsets, NULL);
220 221
		return KNOT_ENOMEM;
	}
222
	memset(xfer, 0, sizeof(struct ixfrout_proc));
223
	gettimeofday(&xfer->proc.tstamp, NULL);
224
	init_list(&xfer->proc.nodes);
225
	xfer->qdata = qdata;
226

227
	/* Put all changesets to processing queue. */
228 229 230 231 232 233
	xfer->changesets = chgsets;
	knot_changeset_t *chs = NULL;
	WALK_LIST(chs, chgsets->sets) {
		ptrlist_add(&xfer->proc.nodes, chs, mm);
		dbg_ns("%s: preparing %u -> %u\n", __func__, chs->serial_from, chs->serial_to);
	}
234

235 236 237 238 239
	/* Keep first and last serial. */
	chs = HEAD(chgsets->sets);
	xfer->soa_from = chs->soa_from;
	chs = TAIL(chgsets->sets);
	xfer->soa_to = chs->soa_to;
240

241 242 243 244
	/* Set up cleanup callback. */
	qdata->ext = xfer;
	qdata->ext_cleanup = &ixfr_answer_cleanup;

245 246 247
	/* No zone changes during multipacket answer (unlocked in axfr_answer_cleanup) */
	rcu_read_lock();

248 249 250
	return KNOT_EOK;
}

251
static int ixfr_answer_soa(knot_pkt_t *pkt, struct query_data *qdata)
252 253
{
	dbg_ns("%s: answering IXFR/SOA\n", __func__);
254
	if (pkt == NULL || qdata == NULL) {
255 256 257
		return NS_PROC_FAIL;
	}

258 259 260 261 262
	/* Check query. */
	int state = ixfr_query_check(qdata);
	if (state == NS_PROC_FAIL) {
		return state; /* Malformed query. */
	}
263

264
	/* Reserve space for TSIG. */
265
	knot_pkt_reserve(pkt, tsig_wire_maxsize(qdata->sign.tsig_key));
266

267
	/* Guaranteed to have zone contents. */
268 269
	const zone_node_t *apex = qdata->zone->contents->apex;
	knot_rrset_t soa_rr = node_rrset(apex, KNOT_RRTYPE_SOA);
270
	if (knot_rrset_empty(&soa_rr)) {
271 272
		return NS_PROC_FAIL;
	}
273
	int ret = knot_pkt_put(pkt, 0, &soa_rr, 0);
274 275 276 277 278
	if (ret != KNOT_EOK) {
		qdata->rcode = KNOT_RCODE_SERVFAIL;
		return NS_PROC_FAIL;
	}

279
	return NS_PROC_DONE;
280
}
281

282
int ixfr_query(knot_pkt_t *pkt, struct query_data *qdata)
283
{
284
	if (pkt == NULL || qdata == NULL) {
285 286 287 288
		return NS_PROC_FAIL;
	}

	int ret = KNOT_EOK;
289
	struct timeval now = {0};
290
	struct ixfrout_proc *ixfr = (struct ixfrout_proc*)qdata->ext;
291

292 293
	/* If IXFR is disabled, respond with SOA. */
	if (qdata->param->proc_flags & NS_QUERY_NO_IXFR) {
294
		return ixfr_answer_soa(pkt, qdata);
295 296
	}

297 298 299 300 301
	/* Initialize on first call. */
	if (qdata->ext == NULL) {
		ret = ixfr_answer_init(qdata);
		switch(ret) {
		case KNOT_EOK:      /* OK */
302 303 304 305
			ixfr = (struct ixfrout_proc*)qdata->ext;
			IXFROUT_LOG(LOG_INFO, "Started (serial %u -> %u).",
			            knot_soa_serial(&ixfr->soa_from->rrs),
			            knot_soa_serial(&ixfr->soa_to->rrs));
306 307
			break;
		case KNOT_EUPTODATE: /* Our zone is same age/older, send SOA. */
308
			IXFROUT_LOG(LOG_INFO, "Zone is up-to-date.");
309
			return ixfr_answer_soa(pkt, qdata);
310 311
		case KNOT_ERANGE:   /* No history -> AXFR. */
		case KNOT_ENOENT:
312
			IXFROUT_LOG(LOG_INFO, "Incomplete history, fallback to AXFR.");
313
			qdata->packet_type = KNOT_QUERY_AXFR; /* Solve as AXFR. */
314
			return axfr_query(pkt, qdata);
315
		default:            /* Server errors. */
316
			IXFROUT_LOG(LOG_ERR, "Failed to start (%s).", knot_strerror(ret));
317 318 319
			return NS_PROC_FAIL;
		}
	}
320

321
	/* Reserve space for TSIG. */
322
	knot_pkt_reserve(pkt, tsig_wire_maxsize(qdata->sign.tsig_key));
323 324

	/* Answer current packet (or continue). */
325
	ret = xfr_process_list(pkt, &ixfr_process_changeset, qdata);
326 327 328 329
	switch(ret) {
	case KNOT_ESPACE: /* Couldn't write more, send packet and continue. */
		return NS_PROC_FULL; /* Check for more. */
	case KNOT_EOK:    /* Last response. */
330
		gettimeofday(&now, NULL);
331 332 333
		IXFROUT_LOG(LOG_INFO, "Finished in %.02fs (%u messages, ~%.01fkB).",
		            time_diff(&ixfr->proc.tstamp, &now) / 1000.0,
		            ixfr->proc.npkts, ixfr->proc.nbytes / 1024.0);
334
		ret = NS_PROC_DONE;
335 336
		break;
	default:          /* Generic error. */
337
		IXFROUT_LOG(LOG_ERR, "%s", knot_strerror(ret));
338 339 340 341 342 343
		ret = NS_PROC_FAIL;
		break;
	}

	return ret;
}
344

345 346 347 348 349 350 351 352 353 354 355
static void ixfrin_cleanup(struct answer_data *data)
{
	struct ixfrin_proc *proc = data->ext;
	if (proc) {
		knot_changesets_free(&proc->changesets, data->mm);
		mm_free(data->mm, proc);
		data->ext = NULL;
	}
}

static int ixfrin_answer_init(struct answer_data *data)
356
{
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389
	struct ixfrin_proc *proc = mm_alloc(data->mm, sizeof(struct ixfrin_proc));
	data->ext = malloc(sizeof(struct ixfrin_proc));
	if (proc == NULL) {
		return KNOT_ENOMEM;
	}
	memset(proc, 0, sizeof(struct ixfrin_proc));

	proc->changesets = knot_changesets_create(0);
	if (proc->changesets == NULL) {
		mm_free(data->mm, proc);
		return KNOT_ENOMEM;
	}
	proc->state = IXFR_START;
	proc->zone = data->param->zone;

	data->ext = proc;
	data->ext_cleanup = &ixfrin_cleanup;

	return KNOT_EOK;
}

static int ixfrin_finalize(struct answer_data *adata)
{
	struct ixfrin_proc *proc = adata->ext;
	zone_t *zone = proc->zone;
	knot_changesets_t *changesets = proc->changesets;

#warning if we need to check serials, here's the place

	if (knot_changesets_empty(changesets) || proc->state != IXFR_DONE) {
		ixfrin_cleanup(adata);
		IXFRIN_LOG(LOG_INFO, "Fallback to AXFR.");
		return KNOT_ENOIXFR;
390 391
	}

392 393 394
	int ret = zone_change_apply_and_store(changesets, zone, "IXFR", adata->mm);
	if (ret != KNOT_EOK) {
		free(proc);
395
		return ret;
396
	}
397

398 399
	proc->changesets = NULL; // Free'd by apply_and_store()
	ixfrin_cleanup(adata);
400

401 402
	IXFRIN_LOG(LOG_INFO, "Finished.");
#warning TODO: schedule zone events, count transfer size and message count, time
403

404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
	return KNOT_EOK;
}

int ixfrin_process_answer(knot_pkt_t *pkt, struct answer_data *adata)
{
	if (adata->ext == NULL) {
		IXFRIN_LOG(LOG_INFO, "Starting.");
		// First packet with IXFR, init context
		int ret = ixfrin_answer_init(adata);
		if (ret != KNOT_EOK) {
			IXFRIN_LOG(LOG_ERR, "Failed - %s", knot_strerror(ret));
			return NS_PROC_FAIL;
		}
	}

	int ret = xfrin_process_ixfr_packet(pkt, (struct ixfrin_proc *)adata->ext);
	if (ret == NS_PROC_DONE) {
		int fret = ixfrin_finalize(adata);
#warning get rid of this rcode mix
		if (fret != KNOT_EOK) {
			if (fret != KNOT_ENOIXFR) {
				ret = NS_PROC_FAIL;
426
			} else {
427
				return KNOT_ENOIXFR;
428 429 430 431
			}
		}
	}

432 433 434
	if (ret == NS_PROC_FAIL) {
		IXFRIN_LOG(LOG_ERR, "Failed.");
	}
435 436 437 438

	return ret;
}

439
#undef IXFROUT_LOG